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

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

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

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

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

  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'
    }

    משתמשים ביחסי תלות מסוג implementation במקום ביחסי תלות מסוג androidTestImplementation. אם משתמשים ב-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. מבנה האפליקציה עם מודולים של Gradle מסוג :app,‏ :microbenchmark ו-:benchmarkable, שמאפשרים ל-Microbenchmarks לבצע השוואת ביצועים של קוד במודול :benchmarkable.

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

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

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

  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')
}

יצירת כיתת Microbenchmark

בדיקות ביצועים הן בדיקות אינסטרומנטציה סטנדרטיות. כדי ליצור נקודת השוואה, משתמשים במחלקה 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, ודוח השוואה מלא עם מדדים נוספים ומידע על המכשיר זמין בפורמט JSON.

תוצאות של Microbenchmark
איור 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_.