כתיבת נתוני מיקרובנצ'מרק

בקטע מדריך למתחילים מוסבר איך להשתמש בספריית Microbenchmark על ידי הוספת שינויים לקוד האפליקציה. כדי ללמוד איך להשלים הגדרה מלאה עם שינויים מורכבים יותר בבסיס הקוד, אפשר לעיין בקטע הגדרה מלאה של פרויקט.

מדריך למתחילים

בקטע הזה מוסבר איך לנסות את ההשוואה לשוק ולהריץ מדידות חד-פעמיות בלי להעביר קוד למודולים. כדי לקבל תוצאות מדויקות של הביצועים, צריך להשבית את ניפוי הבאגים באפליקציה. לכן, כדאי לשמור את השינויים בעותק מקומי ולא לבצע commit שלהם במערכת בקרת המקור.

כדי לבצע השוואה חד-פעמית לשוק:

  1. מוסיפים את הספרייה לקובץ build.gradle או build.gradle.kts של המודול:

    Kotlin

    dependencies {
        implementation("androidx.benchmark:benchmark-junit4:1.2.4")
    }

    מגניב

    dependencies {
        implementation 'androidx.benchmark:benchmark-junit4:1.2.4'
    }

    במקום להשתמש בתלות androidTestImplementation, צריך להשתמש בתלות implementation. אם משתמשים ב-androidTestImplementation, ההשוואות לא יפעלו כי המניפסט של הספרייה לא מוזג עם המניפסט של האפליקציה.

  2. מעדכנים את סוג ה-build‏ debug כך שלא ניתן יהיה לבצע בו ניפוי באגים:

    Kotlin

    android {
        ...
        buildTypes {
            debug {
                isDebuggable = false
            }
        }
    }

    מגניב

    android {
        ...
        buildTypes {
            debug {
                debuggable false
            }
        }
    }
  3. צריך לשנות את הערך של testInstrumentationRunner כך שיהיה AndroidBenchmarkRunner:

    Kotlin

    android {
        ...
        defaultConfig {
            testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
        }
    }

    מגניב

    android {
        ...
        defaultConfig {
            testInstrumentationRunner "androidx.benchmark.junit4.AndroidBenchmarkRunner"
        }
    }
  4. מוסיפים מופע של BenchmarkRule בקובץ בדיקה בספרייה androidTest כדי להוסיף את נקודת ההשוואה. מידע נוסף על כתיבת נקודות השוואה לשוק זמין במאמר בנושא יצירת מחלקה של Microbenchmark.

    בקטע הקוד הבא מוצג איך מוסיפים מדד השוואה לבדיקה עם מכשור:

    Kotlin

    @RunWith(AndroidJUnit4::class)
    class SampleBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()
    
        @Test
        fun benchmarkSomeWork() {
            benchmarkRule.measureRepeated {
                doSomeWork()
            }
        }
    }

    Java

    @RunWith(AndroidJUnit4.class)
    class SampleBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
        @Test
        public void benchmarkSomeWork() {
                BenchmarkRuleKt.measureRepeated(
                    (Function1<BenchmarkRule.Scope, Unit>) scope -> doSomeWork()
                );
           }
        }
    }

כדי ללמוד איך לכתוב בדיקת השוואה לשוק, אפשר לדלג אל יצירת מחלקה של Microbenchmark.

הגדרה מלאה של הפרויקט

כדי להגדיר בנצ'מרקינג רגיל ולא בנצ'מרקינג חד-פעמי, צריך לבודד את הבנצ'מרקים למודול משלהם. כך תוכלו לוודא שההגדרה שלהם, כמו הגדרת debuggable לערך false, נפרדת מבדיקות רגילות.

הספרייה Microbenchmark מריצה את הקוד שלכם ישירות, לכן צריך להציב את הקוד שרוצים לבצע לו בנצ'מרקינג במודול Gradle נפרד ולהגדיר תלות במודול הזה כמו שמוצג באיור 1.

מבנה האפליקציה
איור 1. מבנה האפליקציה עם מודולים של :app,‏ :microbenchmark ו-:benchmarkable Gradle, שמאפשרים ל-Microbenchmarks לבצע בדיקת ביצועים של קוד במודול :benchmarkable.

כדי להוסיף מודול Gradle חדש, אפשר להשתמש באשף המודולים ב-Android Studio. אשף יוצר מודול שמוגדר מראש להשוואה לשוק, עם ספריית השוואה לשוק שנוספה ועם debuggable שמוגדר ל-false.

  1. לוחצים לחיצה ימנית על הפרויקט או המודול בחלונית Project (פרויקט) ב-Android Studio ולוחצים על New > Module (חדש > מודול).

  2. בחלונית Templates (תבניות), בוחרים באפשרות Benchmark (השוואה לשוק).

  3. בוחרים באפשרות Microbenchmark (מיקרו-מדד השוואה) כסוג מודול מדד ההשוואה.

  4. מקלידים microbenchmark בשם המודול.

  5. לוחצים על סיום.

הגדרת מודול חדש של ספרייה
איור 2. מוסיפים מודול Gradle חדש ב-Android Studio Bumblebee.

אחרי שיוצרים את המודול, משנים את הקובץ build.gradle או build.gradle.kts ומוסיפים את androidTestImplementation למודול שמכיל קוד להשוואה:

Kotlin

dependencies {
    // The module name might be different.
    androidTestImplementation(project(":benchmarkable"))
}

מגניב

dependencies {
    // The module name might be different.
    androidTestImplementation project(':benchmarkable')
}

יצירת מחלקה של מיקרו-בנצ'מרק

בדיקות השוואה הן בדיקות אינסטרומנטציה רגילות. כדי ליצור מדד השוואה, משתמשים במחלקה BenchmarkRule שסופקה על ידי הספרייה. כדי להשוות פעילויות לנקודת השוואה, משתמשים בפונקציה ActivityScenario או ActivityScenarioRule. כדי להשוות בין קוד ממשק משתמש, משתמשים ב-@UiThreadTest.

הקוד הבא מציג מדד השוואה לדוגמה:

Kotlin

@RunWith(AndroidJUnit4::class)
class SampleBenchmark {
    @get:Rule
    val benchmarkRule = BenchmarkRule()

    @Test
    fun benchmarkSomeWork() {
        benchmarkRule.measureRepeated {
            doSomeWork()
        }
    }
}
    

Java

@RunWith(AndroidJUnit4.class)
class SampleBenchmark {
    @Rule
    public BenchmarkRule benchmarkRule = new BenchmarkRule();

    @Test
    public void benchmarkSomeWork() {
        final BenchmarkState state = benchmarkRule.getState();
        while (state.keepRunning()) {
            doSomeWork();
        }
    }
}
    

השבתת התזמון של ההגדרה

אפשר להשבית את התזמון של קטעי קוד שלא רוצים למדוד באמצעות הבלוק runWithTimingDisabled{}. הקטעים האלה בדרך כלל מייצגים קוד שצריך להריץ בכל איטרציה של ההשוואה לשוק.

Kotlin

// using random with the same seed, so that it generates the same data every run
private val random = Random(0)

// create the array once and just copy it in benchmarks
private val unsorted = IntArray(10_000) { random.nextInt() }

@Test
fun benchmark_quickSort() {
    // ...
    benchmarkRule.measureRepeated {
        // copy the array with timing disabled to measure only the algorithm itself
        listToSort = runWithTimingDisabled { unsorted.copyOf() }

        // sort the array in place and measure how long it takes
        SortingAlgorithms.quickSort(listToSort)
    }

    // assert only once not to add overhead to the benchmarks
    assertTrue(listToSort.isSorted)
}
    

Java

private final int[] unsorted = new int[10000];

public SampleBenchmark() {
    // Use random with the same seed, so that it generates the same data every
    // run.
    Random random = new Random(0);

    // Create the array once and copy it in benchmarks.
    Arrays.setAll(unsorted, (index) -> random.nextInt());
}

@Test
public void benchmark_quickSort() {
    final BenchmarkState state = benchmarkRule.getState();

    int[] listToSort = new int[0];

    while (state.keepRunning()) {
        
        // Copy the array with timing disabled to measure only the algorithm
        // itself.
        state.pauseTiming();
        listToSort = Arrays.copyOf(unsorted, 10000);
        state.resumeTiming();
        
        // Sort the array in place and measure how long it takes.
        SortingAlgorithms.quickSort(listToSort);
    }

    // Assert only once, not to add overhead to the benchmarks.
    assertTrue(SortingAlgorithmsKt.isSorted(listToSort));
}
    

כדאי לנסות לצמצם את כמות העבודה שמתבצעת בתוך הבלוק measureRepeated ובתוך runWithTimingDisabled. הבלוק measureRepeated מופעל כמה פעמים, והוא יכול להשפיע על הזמן הכולל שנדרש להפעלת הבדיקה. אם צריך לאמת חלק מהתוצאות של בדיקת ההשוואה, אפשר לאמת את התוצאה האחרונה במקום לעשות את זה בכל איטרציה של בדיקת ההשוואה.

הרצת ההשוואה לשוק

ב-Android Studio, מריצים את הבדיקה כמו שמריצים כל @Test באמצעות הפעולה בסרגל הצד שליד מחלקת הבדיקה או המתודה, כמו שמוצג באיור 3.

הרצת Microbenchmark
איור 3. מריצים בדיקת Microbenchmark באמצעות הפעולה שמופיעה בשולי עורך הקוד לצד מחלקת בדיקה.

לחלופין, משורת הפקודה, מריצים את הפקודה connectedCheck כדי להריץ את כל הבדיקות ממודול Gradle שצוין:

./gradlew benchmark:connectedCheck

או בדיקה אחת:

./gradlew benchmark:connectedCheck -P android.testInstrumentationRunnerArguments.class=com.example.benchmark.SampleBenchmark#benchmarkSomeWork

תוצאות ההשוואה לשוק

אחרי הפעלה מוצלחת של Microbenchmark, המדדים מוצגים ישירות ב-Android Studio, ודוח השוואה (benchmark) מלא עם מדדים נוספים ופרטי המכשיר זמין בפורמט JSON.

תוצאות של מיקרו-בנצ'מרק
איור 4. תוצאות של Microbenchmark.

גם דוחות JSON וכל עקבות התיעוד מועתקים באופן אוטומטי מהמכשיר למארח. הן נכתבות במחשב המארח במיקום הבא:

project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/

כברירת מחדל, דוח ה-JSON נכתב לדיסק במכשיר בתיקיית המדיה החיצונית המשותפת של קובץ ה-APK של הבדיקה, שבדרך כלל נמצאת ב-/storage/emulated/0/Android/media/**app_id**/**app_id**-benchmarkData.json.

שגיאות בהגדרות

הספרייה מזהה את התנאים הבאים כדי לוודא שהפרויקט והסביבה מוגדרים לביצועים מדויקים של הגרסה:

  • האפשרות Debuggable מוגדרת ל-false.
  • נעשה שימוש במכשיר פיזי – אין תמיכה באמולטורים.
  • השעונים נעולים אם המכשיר עבר רוט.
  • רמת הטעינה של הסוללה במכשיר מספיקה, לפחות 25%.

אם אחת מהבדיקות הקודמות נכשלת, בדוח ההשוואה לשוק מופיעה שגיאה כדי למנוע מדידות לא מדויקות.

כדי להגדיר שסוגי שגיאות מסוימים יוצגו כאזהרות ולא יגרמו להפסקת הבדיקה, צריך להעביר את סוג השגיאה ברשימה מופרדת בפסיקים לארגומנט של המכשיר androidx.benchmark.suppressErrors.

אפשר להגדיר את זה באמצעות סקריפט Gradle, כמו בדוגמה הבאה:

Kotlin

android {
    defaultConfig {
       
      testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "DEBUGGABLE,LOW-BATTERY"
    }
}

מגניב

android {
    defaultConfig {
       
      testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "DEBUGGABLE,LOW-BATTERY"
    }
}

אפשר גם להסתיר שגיאות משורת הפקודה:

$ ./gradlew :benchmark:connectedCheck -P andoidtestInstrumentationRunnerArguments.androidx.benchmark.supperssErrors=DEBUGGABLE,LOW-BATTERY

הסתרת השגיאות מאפשרת להריץ את ההשוואה למדד במצב לא מוגדר, והפלט של ההשוואה למדד משנה את השם בכוונה על ידי הוספת השגיאה לשמות הבדיקה. לדוגמה, הפעלת מדד ביצועים שאפשר לנפות בו באגים עם ההשבתה בקטע הקוד הקודם מוסיפה את הקידומת DEBUGGABLE_ לשמות הבדיקות.