عملکرد برنامه را با نمایه های پایه بهبود دهید

۱. قبل از شروع

این آزمایشگاه کد نشان می‌دهد که چگونه پروفایل‌های پایه (Baseline Profiles) را برای بهینه‌سازی عملکرد برنامه خود تولید کنید و چگونه مزایای عملکرد استفاده از پروفایل‌های پایه را تأیید کنید.

آنچه نیاز دارید

کاری که انجام خواهید داد

  • پروژه را طوری تنظیم کنید که از مولدهای پروفایل‌های پایه (Baseline Profiles) استفاده کند.
  • ایجاد پروفایل‌های پایه برای بهینه‌سازی عملکرد راه‌اندازی و پیمایش برنامه.
  • افزایش عملکرد را با کتابخانه Jetpack Macrobenchmark تأیید کنید.

آنچه یاد خواهید گرفت

  • پروفایل‌های پایه و اینکه چگونه می‌توانند عملکرد برنامه را بهبود بخشند.
  • نحوه تولید پروفایل‌های پایه
  • افزایش عملکرد پروفایل‌های پایه.

۲. راه‌اندازی

برای شروع، مخزن گیت‌هاب را از خط فرمان با استفاده از دستور زیر کلون کنید:

$ git clone https://github.com/android/codelab-android-performance.git

روش دیگر، می‌توانید دو فایل زیپ را دانلود کنید:

باز کردن پروژه در اندروید استودیو

  1. در پنجره Welcome to Android Studio، گزینه 61d0a4432ef6d396.png باز کردن یک پروژه موجود .
  2. پوشه [Download Location]/codelab-android-performance/baseline-profiles را انتخاب کنید. مطمئن شوید که پوشه baseline-profiles را انتخاب کرده‌اید.
  3. وقتی اندروید استودیو پروژه را ایمپورت می‌کند، مطمئن شوید که می‌توانید ماژول app را برای ساخت برنامه نمونه‌ای که بعداً با آن کار می‌کنید، اجرا کنید.

برنامه نمونه

در این آزمایشگاه کد، شما با برنامه نمونه JetSnack کار می‌کنید. این یک برنامه سفارش مجازی میان وعده است که از Jetpack Compose استفاده می‌کند.

برای سنجش عملکرد برنامه، باید ساختار رابط کاربری و نحوه رفتار برنامه را درک کنید تا بتوانید از طریق بنچمارک‌ها به عناصر رابط کاربری دسترسی پیدا کنید. برنامه را اجرا کنید و با سفارش تنقلات با صفحات اولیه آشنا شوید. نیازی نیست جزئیات نحوه معماری برنامه را بدانید.

23633b02ac7ce1bc.png

۳. پروفایل‌های پایه چیستند؟

پروفایل‌های پایه با اجتناب از تفسیر و مراحل کامپایل درجا (JIT) برای مسیرهای کد گنجانده شده، سرعت اجرای کد را از اولین راه‌اندازی حدود 30٪ بهبود می‌بخشند. با ارسال یک پروفایل پایه در یک برنامه یا کتابخانه، Android Runtime (ART) می‌تواند مسیرهای کد گنجانده شده را از طریق کامپایل Ahead of Time (AOT) بهینه کند و بهبود عملکرد را برای هر کاربر جدید و در هر به‌روزرسانی برنامه فراهم کند. این بهینه‌سازی هدایت‌شده توسط پروفایل (PGO) به برنامه‌ها اجازه می‌دهد تا راه‌اندازی را بهینه کنند، خطاهای تعاملی را کاهش دهند و عملکرد کلی زمان اجرا را برای کاربران نهایی از اولین راه‌اندازی بهبود بخشند.

با یک نمایه پایه، تمام تعاملات کاربر - مانند راه‌اندازی برنامه، پیمایش بین صفحات یا پیمایش محتوا - از اولین باری که اجرا می‌شوند، روان‌تر می‌شوند. افزایش سرعت و پاسخگویی یک برنامه منجر به افزایش کاربران فعال روزانه و میانگین نرخ بازگشت بازدید بالاتر می‌شود.

پروفایل‌های پایه با ارائه تعاملات مشترک کاربر که زمان اجرای برنامه را از اولین راه‌اندازی بهبود می‌بخشد، به هدایت بهینه‌سازی فراتر از راه‌اندازی برنامه کمک می‌کنند. کامپایل AOT هدایت‌شده به دستگاه‌های کاربر متکی نیست و می‌تواند یک بار در هر انتشار، به جای دستگاه تلفن همراه، روی یک دستگاه توسعه انجام شود. با ارسال نسخه‌ها با پروفایل پایه، بهینه‌سازی‌های برنامه بسیار سریع‌تر از زمانی که فقط به پروفایل‌های ابری تکیه می‌شود، در دسترس قرار می‌گیرند.

وقتی از Baseline Profile استفاده نمی‌شود، تمام کدهای برنامه پس از تفسیر، در حافظه کامپایل می‌شوند یا وقتی دستگاه بیکار است، در پس‌زمینه در یک فایل odex کامپایل می‌شوند. در این صورت، کاربران ممکن است هنگام اجرای یک برنامه پس از نصب یا به‌روزرسانی آن برای اولین بار، قبل از بهینه‌سازی مسیرهای جدید، تجربه‌ای نه چندان مطلوب داشته باشند.

۴. ماژول تولیدکننده‌ی پروفایل پایه را تنظیم کنید

شما می‌توانید پروفایل‌های پایه را با یک کلاس تست ابزار دقیق تولید کنید که نیاز به اضافه کردن یک ماژول Gradle جدید به پروژه شما دارد. ساده‌ترین راه برای اضافه کردن آن به پروژه شما، استفاده از ویزارد ماژول اندروید استودیو است که با اندروید استودیو Hedgehog یا نسخه‌های بالاتر ارائه می‌شود.

با کلیک راست روی پروژه یا ماژول خود در پنل پروژه ، پنجره‌ی ویزارد ماژول جدید را باز کنید و New > Module را انتخاب کنید.

۲۳۲b04efef485e9c.png

از پنجره باز شده، از قسمت Templates، گزینه Baseline Profile Generator را انتخاب کنید.

b191fe07969e8c26.png

علاوه بر پارامترهای معمول مانند نام ماژول، نام بسته، زبان یا زبان پیکربندی ساخت، دو ورودی وجود دارد که برای یک ماژول جدید معمول نیستند: Target application و Use Gradle Managed Device .

برنامه‌ی هدف ، ماژول برنامه‌ای است که برای تولید پروفایل‌های پایه برای آن استفاده می‌شود. اگر بیش از یک ماژول برنامه در پروژه‌ی خود دارید، انتخاب کنید که می‌خواهید مولدها برای کدام یک اجرا شوند.

کادر انتخاب «استفاده از دستگاه مدیریت‌شده‌ی گرادل» ماژول را طوری تنظیم می‌کند که مولدهای پروفایل پایه را روی شبیه‌سازهای اندروید که به‌طور خودکار مدیریت می‌شوند، اجرا کند. می‌توانید اطلاعات بیشتر در مورد دستگاه‌های مدیریت‌شده‌ی گرادل را در بخش «مقیاس‌بندی تست‌های خود با دستگاه‌های مدیریت‌شده‌ی گرادل» مطالعه کنید. اگر این کادر را بردارید، مولدها از هر دستگاه متصلی استفاده می‌کنند.

پس از تعریف تمام جزئیات مربوط به ماژول جدید، برای ادامه ایجاد ماژول، روی Finish کلیک کنید.

تغییرات ایجاد شده توسط ویزارد ماژول

ویزارد ماژول چندین تغییر در پروژه شما ایجاد می‌کند.

این یک ماژول Gradle با نام baselineprofile یا نامی که در ویزارد انتخاب می‌کنید، اضافه می‌کند.

این ماژول از افزونه‌ی com.android.test استفاده می‌کند که به Gradle می‌گوید آن را در برنامه‌ی شما قرار ندهد، بنابراین فقط می‌تواند شامل کد تست یا بنچمارک‌ها باشد. همچنین از افزونه‌ی androidx.baselineprofile استفاده می‌کند که امکان تولید خودکار پروفایل‌های پایه را فراهم می‌کند.

این ویزارد همچنین تغییراتی را در ماژول برنامه هدف انتخابی شما ایجاد می‌کند. به طور خاص، افزونه androidx.baselineprofile را اعمال می‌کند، وابستگی androidx.profileinstaller را اضافه می‌کند و وابستگی baselineProfile را به ماژول تازه ایجاد شده build.gradle(.kts) اضافه می‌کند:

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

اضافه کردن وابستگی androidx.profileinstaller به شما امکان می‌دهد موارد زیر را انجام دهید:

  • به صورت محلی، بهبود عملکرد پروفایل‌های پایه تولید شده را تأیید کنید.
  • از پروفایل‌های پایه در اندروید ۷ (سطح API 24) و اندروید ۸ (سطح API 26) استفاده کنید، که از پروفایل‌های ابری پشتیبانی نمی‌کنند.
  • از نمایه‌های پایه در دستگاه‌هایی که خدمات Google Play ندارند استفاده کنید.

وابستگی baselineProfile(project(":baselineprofile")) به Gradle اجازه می‌دهد تا بداند از کدام ماژول باید Baseline Profile های تولید شده را دریافت کند.

حالا که پروژه را تنظیم کرده‌اید، یک کلاس تولیدکننده‌ی پروفایل‌های پایه بنویسید.

۵. یک مولد پروفایل پایه بنویسید

معمولاً، شما برای سفرهای معمول کاربر در اپلیکیشن خود، پروفایل‌های پایه ایجاد می‌کنید.

ویزارد ماژول یک کلاس تست BaselineProfileGenerator پایه ایجاد می‌کند که قادر به تولید Baseline Profile برای شروع برنامه شما است و به شکل زیر خواهد بود:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

این کلاس از یک قانون تست BaselineProfileRule استفاده می‌کند و شامل یک متد تست برای تولید پروفایل است. نقطه ورودی برای تولید پروفایل، تابع collect() است. این تابع فقط به دو پارامتر نیاز دارد:

  • packageName : بسته‌ی برنامه‌ی شما.
  • profileBlock : آخرین پارامتر لامبدا.

در لامبدا profileBlock ، تعاملاتی را مشخص می‌کنید که سفرهای معمول کاربر در برنامه شما را پوشش می‌دهند. این کتابخانه چندین بار profileBlock را اجرا می‌کند، کلاس‌ها و توابع فراخوانی شده را جمع‌آوری می‌کند و Baseline Profile را روی دستگاه با کدی که باید بهینه شود، تولید می‌کند.

به طور پیش‌فرض، کلاس مولد ایجاد شده شامل تعاملاتی برای شروع Activity پیش‌فرض شما است و منتظر می‌ماند تا اولین فریم برنامه شما با استفاده از متد startActivityAndWait() رندر شود.

ژنراتور را با سفرهای سفارشی گسترش دهید

می‌توانید ببینید که کلاس تولید شده شامل برخی TODO نیز می‌شود تا تعاملات بیشتری برای بهینه‌سازی مراحل پیشرفته برنامه شما بنویسد. این کار توصیه می‌شود تا بتوانید عملکرد را فراتر از شروع برنامه بهینه کنید.

در اپلیکیشن نمونه ما، می‌توانید این مسیرها را با انجام موارد زیر شناسایی کنید:

  1. برنامه را اجرا کنید. این بخش از قبل توسط کلاس تولید شده پوشش داده شده است.
  2. صبر کنید تا محتوا به صورت ناهمزمان بارگذاری شود.
  3. فهرست میان وعده‌ها را ورق بزنید.
  4. به جزئیات میان وعده بروید.

مولد را طوری تغییر دهید که شامل توابع مشخص شده‌ای باشد که مسیرهای معمول در قطعه کد زیر را پوشش می‌دهند:

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

حالا، برای هر مسیر ذکر شده، تعاملاتی بنویسید. می‌توانید آن را به عنوان تابع افزونه‌ی MacrobenchmarkScope بنویسید تا به پارامترها و توابعی که ارائه می‌دهد دسترسی داشته باشید. نوشتن آن به این روش به شما امکان می‌دهد تا از تعاملات با معیارها برای تأیید افزایش عملکرد، دوباره استفاده کنید.

منتظر محتوای ناهمزمان باشید

بسیاری از برنامه‌ها نوعی بارگذاری ناهمزمان در هنگام راه‌اندازی برنامه دارند که به عنوان وضعیت کاملاً نمایش داده شده نیز شناخته می‌شود و به سیستم می‌گوید چه زمانی محتوا بارگذاری و رندر شده است و کاربر می‌تواند با آن تعامل داشته باشد. منتظر وضعیت در مولد ( waitForAsyncContent ) با این تعاملات باشید:

  1. لیست میان وعده های غذایی را پیدا کنید.
  2. صبر کنید تا برخی از موارد موجود در لیست روی صفحه نمایش داده شوند.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

پیمایش فهرست سفر

برای پیمایش فهرست میان وعده ( scrollSnackListJourney )، می‌توانید این تعاملات را دنبال کنید:

  1. عنصر رابط کاربری فهرست تنقلات را پیدا کنید.
  2. حاشیه‌های حرکتی را طوری تنظیم کنید که ناوبری سیستم را فعال نکنند.
  3. لیست را اسکرول کنید و صبر کنید تا رابط کاربری مرتب شود.
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

به جزئیات سفر بروید

آخرین سفر ( goToSnackDetailJourney ) این تعاملات را پیاده‌سازی می‌کند:

  1. لیست میان وعده‌ها و تمام اقلام میان وعده‌ای که می‌توانید با آنها کار کنید را پیدا کنید.
  2. یک مورد را از لیست انتخاب کنید.
  3. روی مورد کلیک کنید و منتظر بمانید تا صفحه جزئیات بارگذاری شود. می‌توانید از این واقعیت که لیست میان وعده دیگر روی صفحه نمایش داده نمی‌شود، استفاده کنید.
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

پس از اینکه تمام تعاملات مورد نیاز برای آماده شدن مولد پروفایل پایه خود را تعریف کردید، باید دستگاهی را که روی آن اجرا می‌شود، تعریف کنید.

۶. دستگاهی را برای راه‌اندازی ژنراتور آماده کنید

برای تولید پروفایل‌های پایه، توصیه می‌کنیم از یک شبیه‌ساز مانند Gradle Managed Device یا دستگاهی که اندروید ۱۳ (API 33) یا بالاتر را اجرا می‌کند، استفاده کنید.

برای اینکه این فرآیند قابل تکرار باشد و تولید پروفایل‌های پایه خودکار شود، می‌توانید از Gradle Managed Devices استفاده کنید. Gradle Managed Devices به شما امکان می‌دهد تست‌ها را روی یک شبیه‌ساز اندروید اجرا کنید، بدون اینکه نیاز باشد آن را به صورت دستی راه‌اندازی و از کار بیندازید. می‌توانید اطلاعات بیشتر در مورد Gradle Managed Devices را در بخش Scale your tests with Gradle Managed Devices بیابید.

برای تعریف یک دستگاه مدیریت‌شده‌ی Gradle، تعریف آن را به فایل build.gradle.kts ماژول :baselineprofile اضافه کنید، همانطور که در قطعه کد زیر نشان داده شده است:

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  } 
}

در این حالت، ما از اندروید ۱۱ (سطح API 31) استفاده می‌کنیم و تصویر سیستم aosp قابلیت دسترسی روت شده را دارد.

در مرحله بعد، افزونه Gradle پروفایل پایه را طوری پیکربندی کنید که از Gradle Managed Device تعریف شده استفاده کند. برای انجام این کار، نام دستگاه را به ویژگی managedDevices اضافه کنید و useConnectedDevices همانطور که در قطعه کد زیر نشان داده شده است، غیرفعال کنید:

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

در مرحله بعد، نمایه پایه (Baseline Profile) را ایجاد کنید.

۷. ایجاد پروفایل پایه

پس از آماده شدن دستگاه، می‌توانید پروفایل پایه (Baseline Profile) را ایجاد کنید. افزونه Gradle پروفایل پایه، وظایف Gradle را ایجاد می‌کند تا کل فرآیند اجرای کلاس تست ژنراتور و اعمال پروفایل‌های پایه تولید شده در برنامه شما را خودکار کند.

ویزارد ماژول جدید، پیکربندی اجرا را ایجاد کرد تا بتواند به سرعت وظیفه Gradle را با تمام پارامترهای لازم اجرا کند، بدون اینکه نیازی به جابجایی بین ترمینال و اندروید استودیو باشد.

برای اجرای آن، پیکربندی اجرای Generate Baseline Profile را پیدا کرده و روی دکمه Run کلیک کنید. 599be5a3531f863b.png .

6911ecf1307a213f.png

این وظیفه، تصویر شبیه‌ساز تعریف‌شده‌ی قبلی را آغاز می‌کند. تعاملات را از کلاس تست BaselineProfileGenerator چندین بار اجرا می‌کند و پس از آن، شبیه‌ساز را از حالت فشرده خارج کرده و خروجی را به اندروید استودیو ارائه می‌دهد.

زمانی که مولد با موفقیت کار خود را به پایان رساند، افزونه Gradle به طور خودکار فایل baseline-prof.txt تولید شده را در برنامه هدف شما (ماژول :app ) در پوشه src/release/generated/baselineProfile/ قرار می‌دهد.

fa0f52de5d2ce5e8.png

(اختیاری) ژنراتور را از خط فرمان اجرا کنید

به عنوان یک روش جایگزین، می‌توانید مولد را از خط فرمان اجرا کنید. می‌توانید از وظیفه ایجاد شده توسط Gradle Managed Device— :app:generateBaselineProfile استفاده کنید. این دستور تمام تست‌های موجود در پروژه تعریف شده توسط وابستگی baselineProfile(project(:baselineProfile)) را اجرا می‌کند. از آنجا که این ماژول همچنین شامل معیارهایی برای تأیید بعدی دستاوردهای عملکرد است، این تست‌ها با هشداری مبنی بر عدم اجرای معیارها روی شبیه‌ساز، با شکست مواجه می‌شوند.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

برای این کار، می‌توانید تمام مولدهای پروفایل‌های پایه را با آرگومان اجراکننده‌ی ابزار دقیق زیر فیلتر کنید و از تمام بنچمارک‌ها صرف نظر کنید:

کل دستور به شکل زیر در می‌آید:

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

برنامه خود را با پروفایل‌های پایه توزیع کنید

پس از تولید و کپی شدن Baseline Profile در کد منبع برنامه، نسخه نهایی برنامه خود را طبق معمول بسازید. برای توزیع Baseline Profiles بین کاربران خود نیازی به انجام کار اضافی ندارید. آنها توسط Android Gradle Plugin در طول ساخت انتخاب شده و در AAB یا APK شما قرار می‌گیرند. در مرحله بعد، نسخه ساخته شده را در Google Play آپلود کنید.

وقتی کاربران برنامه را نصب می‌کنند یا برنامه را از نسخه قبلی به‌روزرسانی می‌کنند، پروفایل پایه نیز نصب می‌شود که منجر به عملکرد بهتر از اولین اجرای برنامه می‌شود.

مرحله بعدی نشان می‌دهد که چگونه می‌توان میزان بهبود عملکرد برنامه را با پروفایل‌های پایه بررسی کرد.

۸. (اختیاری) سفارشی‌سازی تولید پروفایل‌های پایه

افزونه Gradle پروفایل‌های پایه شامل گزینه‌هایی برای سفارشی‌سازی نحوه تولید پروفایل‌ها برای برآورده کردن نیازهای خاص شما است. می‌توانید رفتار را با بلوک پیکربندی baselineProfile { } در اسکریپت‌های ساخت تغییر دهید.

بلوک پیکربندی درون ماژول :baselineprofile بر نحوه اجرای مولدها با امکان افزودن managedDevices و تصمیم‌گیری در مورد استفاده useConnectedDevices یا Gradle Managed devices تأثیر می‌گذارد.

بلوک پیکربندی درون ماژول target :app تصمیم می‌گیرد که پروفایل‌ها کجا ذخیره شوند یا چگونه تولید شوند. می‌توانید پارامترهای زیر را تغییر دهید:

  • automaticGenerationDuringBuild : در صورت فعال بودن، می‌توانید هنگام ساخت نسخه نهایی، Baseline Profile را ایجاد کنید. این ویژگی هنگام ساخت CI قبل از انتشار برنامه مفید است.
  • saveInSrc : مشخص می‌کند که آیا پروفایل‌های پایه تولید شده در پوشه src/ ذخیره شوند یا خیر. همچنین می‌توانید از پوشه build :baselineprofile به این فایل دسترسی داشته باشید.
  • baselineProfileOutputDir : محل ذخیره پروفایل‌های پایه تولید شده را تعریف می‌کند.
  • mergeIntoMain : به طور پیش‌فرض، پروفایل‌های پایه برای هر نوع ساخت (نوع محصول و نوع ساخت) ایجاد می‌شوند. اگر می‌خواهید همه پروفایل‌ها را در src/main ادغام کنید، می‌توانید این کار را با فعال کردن این پرچم انجام دهید.
  • filter : می‌توانید مشخص کنید که کدام کلاس‌ها یا متدها از پروفایل‌های پایه تولید شده حذف یا اضافه شوند. این می‌تواند برای توسعه‌دهندگان کتابخانه‌ای که می‌خواهند فقط کد کتابخانه در آنها گنجانده شود، مفید باشد.

۹. بهبود عملکرد راه‌اندازی را تأیید کنید

پس از ایجاد نمایه پایه و افزودن آن به برنامه خود، تأیید کنید که تأثیر مورد نظر شما را بر عملکرد برنامه شما دارد.

ویزارد ماژول جدید، یک کلاس بنچمارک به نام StartupBenchmarks ایجاد می‌کند. این کلاس شامل یک بنچمارک برای اندازه‌گیری زمان راه‌اندازی برنامه و مقایسه آن با زمانی است که برنامه از پروفایل‌های پایه (Baseline Profiles) استفاده می‌کند.

کلاس به شکل زیر در می‌آید:

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

این برنامه MacrobenchmarkRule استفاده می‌کند که قادر به اجرای بنچمارک‌ها برای برنامه شما و جمع‌آوری معیارهای عملکرد است. نقطه شروع برای نوشتن یک بنچمارک، تابع measureRepeated از این قانون است.

به چندین پارامتر نیاز دارد:

  • packageName: کدام برنامه برای اندازه‌گیری.
  • metrics : چه نوع اطلاعاتی را می‌خواهید در طول بنچمارک اندازه‌گیری کنید.
  • iterations : تعداد دفعاتی که معیار تکرار می‌شود.
  • startupMode : نحوه‌ی شروع برنامه‌ی شما پس از شروع بنچمارک.
  • setupBlock : چه تعاملاتی با برنامه شما باید قبل از اندازه‌گیری انجام شود.
  • measureBlock : تعاملات با برنامه شما که می‌خواهید در طول بنچمارک اندازه‌گیری کنید.

کلاس تست همچنین شامل دو تست است: startupCompilationeNone() و startupCompilationBaselineProfiles() که تابع benchmark() را با compilationMode متفاوت فراخوانی می‌کند.

حالت کامپایل

پارامتر CompilationMode نحوه‌ی پیش‌کامپایل شدن برنامه به کد ماشین را تعریف می‌کند. این پارامتر گزینه‌های زیر را دارد:

  • DEFAULT : در صورت وجود، برنامه را با استفاده از Baseline Profiles تا حدی از قبل کامپایل می‌کند. این در صورتی استفاده می‌شود که هیچ پارامتر compilationMode اعمال نشده باشد.
  • None() : وضعیت کامپایل برنامه را ریست می‌کند و برنامه را از قبل کامپایل نمی‌کند. کامپایل درجا (JIT) همچنان در حین اجرای برنامه فعال است.
  • Partial() : برنامه را با پروفایل‌های پایه یا اجراهای گرم کردن یا هر دو، از قبل کامپایل می‌کند.
  • Full() : کل کد برنامه را از قبل کامپایل می‌کند. این تنها گزینه در اندروید ۶ (API 23) و پایین‌تر است.

اگر می‌خواهید بهینه‌سازی عملکرد برنامه خود را شروع کنید، می‌توانید حالت کامپایل DEFAULT را انتخاب کنید، زیرا عملکرد مشابه زمانی است که برنامه از Google Play نصب شده است. اگر می‌خواهید مزایای عملکرد ارائه شده توسط Baseline Profiles را مقایسه کنید، می‌توانید این کار را با مقایسه نتایج حالت کامپایل None و Partial انجام دهید.

معیار را تغییر دهید تا منتظر محتوا باشید

این بنچمارک‌ها مشابه مولدهای Baseline Profiles و با نوشتن تعاملات با برنامه شما نوشته می‌شوند. به طور پیش‌فرض، بنچمارک‌های ایجاد شده فقط منتظر رندر شدن اولین فریم می‌مانند - مشابه کاری که BaselineProfileGenerator انجام داد - بنابراین توصیه می‌کنیم آن را طوری بهبود دهید که منتظر محتوای ناهمزمان بماند.

شما می‌توانید این کار را با استفاده مجدد از توابع افزونه‌ای که برای مولد می‌نویسید، انجام دهید. از آنجا که این معیار، زمان‌بندی‌های راه‌اندازی را ثبت می‌کند - با استفاده از StartupTimingMetric() - توصیه می‌کنیم فقط انتظار برای محتوای ناهمزمان را در اینجا لحاظ کنید و سپس یک معیار جداگانه برای سایر مسیرهای کاربر تعریف شده در مولد بنویسید.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

اجرای بنچمارک‌ها

شما می‌توانید بنچمارک‌ها را به همان روشی که تست‌های ابزاری را اجرا می‌کنید، اجرا کنید. می‌توانید تابع تست یا کل کلاس را با آیکون ناودانی کنار آن اجرا کنید.

587b04d1a76d1e9d.png

مطمئن شوید که یک دستگاه فیزیکی انتخاب کرده‌اید، زیرا اجرای بنچمارک‌ها روی شبیه‌ساز اندروید در زمان اجرا با اخطاری مبنی بر اینکه بنچمارک ممکن است نتایج نادرستی ارائه دهد، با شکست مواجه می‌شود. در حالی که از نظر فنی می‌توانید آن را روی یک شبیه‌ساز اجرا کنید، در حال اندازه‌گیری عملکرد دستگاه میزبان خود هستید. اگر تحت بار سنگین باشد، بنچمارک‌های شما کندتر و برعکس عمل می‌کنند.

94e0da86b6f399d5.png

پس از اجرای بنچمارک، برنامه شما دوباره ساخته می‌شود و سپس بنچمارک‌های شما را اجرا می‌کند. بنچمارک‌ها بر اساس iterations که تعریف می‌کنید، چندین بار برنامه شما را شروع، متوقف و حتی دوباره نصب می‌کنند.

پس از اتمام بنچمارک‌ها، می‌توانید زمان‌بندی‌ها را در خروجی اندروید استودیو، همانطور که در تصویر زیر نشان داده شده است، مشاهده کنید:

۲۸۲f۹۰d۵f۶ff۵۱۹۶.png

از تصویر صفحه، می‌توانید ببینید که زمان شروع برنامه برای هر CompilationMode متفاوت است. مقادیر میانه در جدول زیر نشان داده شده است:

زمان نمایش اولیه [میلی‌ثانیه]

زمان نمایش کامل [میلی‌ثانیه]

هیچکدام

۲۰۲.۲

۸۱۸.۸

پروفایل‌های پایه

۱۹۳.۷

۶۳۷.۹

بهبود

۴٪

۲۸٪

تفاوت بین حالت‌های کامپایل برای timeToFullDisplay ، ۱۸۰ میلی‌ثانیه است که با داشتن Baseline Profile، حدود ۲۸٪ بهبود یافته است. CompilationNone عملکرد بدتری دارد، زیرا دستگاه باید بیشترین کامپایل JIT را در طول راه‌اندازی برنامه انجام دهد. CompilationBaselineProfiles عملکرد بهتری دارد زیرا کامپایل جزئی با Baseline Profiles AOT کدی را که کاربر به احتمال زیاد از آن استفاده می‌کند کامپایل می‌کند و کد غیر ضروری را از قبل کامپایل نمی‌کند، بنابراین نیازی به بارگذاری فوری ندارد.

۱۰. (اختیاری) بهبود عملکرد پیمایش را تأیید کنید

مشابه مرحله قبل، می‌توانید عملکرد پیمایش را اندازه‌گیری و تأیید کنید. ابتدا، یک کلاس تست ScrollBenchmarks با قانون معیار و دو روش تست که از حالت‌های کامپایل مختلف استفاده می‌کنند، ایجاد کنید:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

از درون متد scroll ، از تابع measureRepeated با پارامترهای مورد نیاز استفاده کنید. برای پارامتر metrics ، از FrameTimingMetric استفاده کنید که مدت زمان تولید فریم‌های رابط کاربری را اندازه‌گیری می‌کند:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

این بار، باید تعاملات را بیشتر بین setupBlock و measureBlock تقسیم کنید تا فقط مدت زمان فریم را در طول طرح‌بندی اول و پیمایش محتوا اندازه‌گیری کنید. بنابراین، توابعی را که صفحه پیش‌فرض را شروع می‌کنند در setupBlock و توابع افزونه از قبل ایجاد شده waitForAsyncContent() و scrollSnackListJourney() را در measureBlock قرار دهید:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

پس از آماده شدن بنچمارک، می‌توانید آن را مانند قبل اجرا کنید تا نتایجی مانند تصویر زیر دریافت کنید:

84aa99247226fc3a.png

تابع FrameTimingMetric مدت زمان فریم‌ها را بر حسب میلی‌ثانیه ( frameDurationCpuMs ) در صدک‌های ۵۰، ۹۰، ۹۵ و ۹۹ام نمایش می‌دهد. در اندروید ۱۲ (سطح API ۳۱) و بالاتر، این تابع همچنین مدت زمانی که فریم‌های شما از حد مجاز بیشتر شده‌اند ( frameOverrunMs ) را نیز برمی‌گرداند. این مقدار می‌تواند منفی باشد، به این معنی که زمان اضافی برای تولید فریم باقی مانده است.

از نتایج، می‌توانید ببینید که CompilationBaselineProfiles به طور متوسط ​​مدت زمان فریم کوتاه‌تری به میزان ۲ میلی‌ثانیه دارد که ممکن است برای کاربران قابل توجه نباشد. با این حال، برای سایر صدک‌ها، نتایج واضح‌تر است. برای P99، تفاوت ۴۳.۵ میلی‌ثانیه است که بیش از ۳ فریم از دست رفته در دستگاهی است که با ۹۰ فریم در ثانیه کار می‌کند. به عنوان مثال، برای پیکسل ۶، ۱۰۰۰ میلی‌ثانیه / ۹۰ فریم در ثانیه = حداکثر زمان رندر یک فریم.

۱۱. تبریک

تبریک می‌گویم، شما این آزمایشگاه کد را با موفقیت به پایان رساندید و عملکرد برنامه خود را با پروفایل‌های پایه بهبود بخشیدید!

منابع اضافی

منابع تکمیلی زیر را ببینید:

اسناد مرجع