持續整合中的基準測試

透過在持續整合 (CI) 中執行基準測試,您可以追蹤效能變化趨勢,並在應用程式發布前找出效能降低的情形或改善項目。本頁面提供在 CI 中執行基準測試的基本資訊。

在開始介紹 CI 中的基準測試前,首先要說明在擷取及評估基準測試結果時,與一般測試有何不同。

模糊結果

雖然基準測試屬於檢測設備測試,但檢測結果並非只有通過或失敗。基準測試會針對執行的特定裝置提供時間評估。以圖形呈現結果變化趨勢可讓您掌握變化,並觀察測量系統中的雜訊。

使用實體裝置

在實體 Android 裝置上執行基準測試。雖然可以在模擬器中執行,但強烈建議不要採用此方式,因為這無法反映實際的使用者體驗,而且只會提供與主機 OS 和硬體功能相關的數據。建議您使用真實裝置或可在真實裝置上執行測試的服務,例如 Firebase Test Lab

執行基準測試

在 CI 管道中執行基準測試,與透過 Android Studio 在本機執行基準測試可能有所不同。在本機上,通常會使用一項 Gradle connectedCheck 任務執行 Android 整合測試。這項任務會自動建構及測試 APK,然後在已連線至 CI 伺服器的裝置中執行測試。在 CI 中執行時,這個流程通常需要分為多個不同的階段。

建構

對 Microbenchmark 程式庫執行 Gradle 任務 assemble[VariantName]AndroidTest,這會建立同時包含應用程式程式碼和測試程式碼的測試 APK。

此外,Macrobenchmark 程式庫規定您必須分別建構目標 APK 和測試 APK。因此,請執行 :app:assemble[VariantName]:macrobenchmark:assemble[VariantName] Gradle 任務。

安裝與執行

這些步驟通常不需要執行 Gradle 任務即可完成。請注意,這些步驟可能會由系統提取,具體情況取決於服務是否允許在真實裝置上執行測試。

針對安裝作業,請使用 adb install 指令,並指定測試 APK 或目標 APK。

執行 adb shell am 檢測指令即可執行所有基準測試:

adb shell am instrument -w com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

使用 Macrobenchmark 程式庫時,請使用一般的 androidx.test.runner.AndroidJUnitRunner 做為檢測執行元件。

您可以使用 -e 引數,傳遞與 Gradle 設定中相同的檢測引數。如需所有檢測引數選項,請參閱 Macrobenchmark 的「Microbenchmark 檢測引數」或「新增檢測引數」相關說明。

舉例來說,您可以設定 dryRunMode 引數以執行 Microbenchmark,做為提取要求驗證程序的一部分。啟用此標記後,Microbenchmark 就只會在單一迴圈中執行,這樣既可驗證執行方式是否正確,也不會花費太多執行時間。

adb shell am instrument -w -e "androidx.benchmark.dryRunMode.enable" "true" com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

如要進一步瞭解如何透過指令列執行檢測設備測試,請參閱「使用 ADB 執行測試」一文。

鎖定時脈

Microbenchmark Gradle 外掛程式提供 ./gradlew lockClocks 指令,可鎖定已解鎖裝置的 CPU 時脈。存取已解鎖裝置 (例如「userdebug」版本) 時,這有助於確保穩定性。如要複製這個指令,可以使用程式庫來源中的 lockClocks.sh 殼層指令碼。

您可以直接透過 Linux 或 Mac 主機執行指令碼,也可以使用一些 ADB 指令將指令碼推送至裝置:

adb push path/lockClocks.sh /data/local/tmp/lockClocks.sh
adb shell /data/local/tmp/lockClocks.sh
adb shell rm /data/local/tmp/lockClocks.sh

如果是直接在主機上執行殼層指令碼,系統會將這些指令分派至已連線的裝置。

如要進一步瞭解鎖定 CPU 時脈的重要性,請參閱「取得一致的基準測試結果」。

收集結果

每次執行完基準測試後,基準測試程式庫都會在 JSON 中輸出測量結果,並將剖析追蹤記錄輸出至 Android 裝置的目錄。Macrobenchmark 程式庫會輸出多個 Perfetto 追蹤記錄檔,每個 MacrobenchmarkRule.measureRepeated 迴圈中每個測量的疊代各有一個追蹤記錄檔。Microbenchmark 則是只會針對每個 BenchmarkRule.measureRepeated 的所有疊代建立一個追蹤記錄檔。剖析追蹤記錄檔也會輸出至同一個目錄。

儲存與尋找檔案

使用 Gradle 執行基準測試時,這些檔案會自動複製到主機電腦的輸出內容目錄 build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/ 底下。

如果使用 adb 指令直接執行,就必須手動提取檔案。根據預設,報表會儲存在裝置端,存放在受測試應用程式外部儲存空間的媒體目錄中。為了方便起見,程式庫會在 Logcat 中顯示檔案路徑。請注意,視執行基準測試的 Android 版本而定,輸出資料夾可能有所不同。

Benchmark: writing results to /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

您也可以使用檢測引數 additionalTestOutputDir,設定基準測試報表在裝置上的儲存位置。該資料夾必須可由應用程式寫入。

adb shell am instrument -w -e additionalTestOutputDir /sdcard/Download/ com.example.benchmark/androidx.benchmark.junit4.AndroidBenchmarkRunner

在 Android 10 (API 級別 29) 以上版本中,系統會預設在儲存空間沙箱中執行應用程式測試,以免應用程式存取其專屬目錄以外的檔案。如要儲存至全域目錄 (例如 /sdcard/Download),請傳遞以下檢測引數:

-e no-isolated-storage true

您也必須在基準測試的資訊清單中,明確允許舊版儲存空間選項:

<application android:requestLegacyExternalStorage="true" ... >

詳情請參閱「暫時不使用限定範圍儲存空間」。

擷取檔案

如要從裝置中擷取產生的檔案,請使用 adb pull 指令,這個指令會提取指定的檔案並存入主機的當前目錄:

adb pull /storage/emulated/0/Android/media/com.example.macrobenchmark/com.example.macrobenchmark-benchmarkData.json

如要從指定資料夾擷取所有 benchmarkData,請參考以下程式碼片段:

# The following command pulls all files ending in -benchmarkData.json from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
adb shell find /sdcard/Download -name "*-benchmarkData.json" | tr -d '\r' | xargs -n1 adb pull

請注意,追蹤記錄檔 (.trace.perfetto-trace) 儲存的資料夾與 benchmarkData.json 相同,因此您可以使用相同的方式進行收集。

基準測試資料範例

基準測試程式庫會產生 JSON 檔案,其中列載的資訊包括執行基準測試所使用的裝置,以及實際執行的基準測試。以下程式碼片段代表所產生的 JSON 檔案:

{
    "context": {
        "build": {
            "brand": "google",
            "device": "blueline",
            "fingerprint": "google/blueline/blueline:12/SP1A.210812.015/7679548:user/release-keys",
            "model": "Pixel 3",
            "version": {
                "sdk": 31
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2803200000,
        "memTotalBytes": 3753299968,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "com.example.macrobenchmark.startup.SampleStartupBenchmark",
            "totalRunTimeNs": 4975598256,
            "metrics": {
                "timeToInitialDisplayMs": {
                    "minimum": 347.881076,
                    "maximum": 347.881076,
                    "median": 347.881076,
                    "runs": [
                        347.881076
                    ]
                }
            },
            "sampledMetrics": {},
            "warmupIterations": 0,
            "repeatIterations": 3,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

其他資源