咱們在 I/O 2019 發佈了 Benchmark 庫的第一個 alpha 版。以後爲了能幫助您在優化代碼時能夠準確地評估性能,咱們就一直在改進 Benchmark 庫。Jetpack Benchmark 是一個運行在 Android 設備上的標準 JUnit 插樁測試 (instrumentation tests),它使用 Benchmark 庫提供的一套規則進行測量和報告:html
@get:Rule val benchmarkRule = BenchmarkRule() @UiThreadTest @Test fun simpleScroll() { benchmarkRule.measureRepeated { // Scroll RecyclerView by one item recyclerView.scrollBy(0, recyclerView.getLastChild().height) } }
△ Github 上的 示例工程android
△ Android Studio 輸出、運行多個基準測試的示例git
Benchmark 庫經過它本身的 JUnit Rule API 處理預熱、檢測配置問題以及評估代碼性能。github
上面介紹的這些在咱們本身的工做環境下用起來很不錯,可是不少基準測試數據其實來自於持續集成 (Continuous Integration, CI) 中對於迴歸模型的檢測。那麼咱們要如何處理 CI 中的基準數據呢?算法
一個工程裏就算有數千個正確性測試,也能夠輕易經過信息摺疊顯示在數據面板上。下面就是咱們在 Jetpack 中的測試信息:數據庫
這裏沒有什麼特別的內容,可是在減小視覺負荷方面使用了兩個常見技巧。首先,這裏以包和類的維度摺疊了包含數千條測試信息的列表;而後,默認狀況下隱藏告終果所有正確的包。就這樣,數十個庫裏接近兩萬個測試結果,就被囊括到了寥寥幾行文字之中。正確性測試的面板很好地控制了所展現的數據規模。緩存
可是基準測試又如何呢?基準測試不會簡單地輸出經過/不經過,每一個測試的結果都是一個標量,這意味着咱們無法簡單地將經過的結果摺疊起來。咱們能夠看一看數據圖表,也許能夠對數據的模式有個直觀的瞭解,畢竟一般狀況下,基準測試的數量要遠少於正確性測試...app
可是您卻只能看到一大堆可見噪聲。就算測試結果從數千減小到數百個,直接看圖表對於數據的分析依然不會有任何幫助。基準測試中保持原有性能結果的數據與測試迴歸的數據所佔據的可視區域相同,因此咱們須要把未出現測試迴歸的數據過濾掉 (這樣測試迴歸的數據才能凸顯出來)。ide
咱們能夠從一些簡單的事情開始,嘗試回到只有經過和不經過的正確性測試。例如能夠把兩次運行的結果降低百分比超過某一閾值的狀況定義爲基準測試的失敗結果。不過因爲方差的緣由,這種方式並不能成功。函數
△ 視圖填充的基準數據容易出現較大方差,可是仍然提供了有用的數據
雖然咱們一直嘗試在基準測試中產生穩定且一致的結果,可是曲線的變化仍然會很大,這主要取決於工做量的大小和所運行的設備。好比說,相比於其餘 CPU 工做量基準測試數據,咱們發現填充視圖的測試結果很是不穩定。而將閾值設置爲百分之一併不能在每一個測試中得到理想的結果,可是咱們也不但願把設定閾值的 (或者基線) 的負擔施加在基準測試的做者身上,由於這個工做不但繁瑣,並且隨着分析規模的增長,其擴展性也相對較差。
當一些測試設備在連續幾個基準測試中產生異常緩慢的結果時,方差也可能會以低頻的大範圍波峯的形式出現。雖然咱們能夠修復其中一些 (例如,防止因電量不足致使核心被禁用時運行測試) ,可是很難避免全部的方差。
△ RecyclerView、Ads-identifier 以及 Room 的一次基準測試中出現的全部峯值——咱們不但願將其做爲迴歸模型報告出來
綜上所述,咱們不能僅經過第 N 次和 N - 1 次 Build 結果就定位一個測試迴歸問題——咱們須要更多上下文信息來輔助決策。
咱們在 Jetpack CI 中進行分步擬合的方法是由 Skia Perf application 提供的。
這個方法是在基準數據中尋找階躍函數。當咱們檢查每一個基準測試的結果序列,能夠嘗試尋找 "階躍" 的上升或降低的數據點做爲特定的 Build 改變基準測試效果的信號。不過咱們也要多看幾個數據點,以確保咱們看到的是多個結果造成的一致的趨勢,而不是偶然現象:
△ 上下文能夠揭示出性能退化幅度較大的位置可能只是基準化分析結果反覆無常的變化而已
那麼咱們如何挑選出這樣一個階躍呢?咱們須要查看變化先後的多個結果:
而後,咱們用下面這段代碼計算測試迴歸的權值:
這裏操做的原理是,經過檢測更改先後的偏差,並對該偏差的平均值的差進行加權,基準的方差越小,咱們就越有信心檢測出細微的測試迴歸。這使得咱們能夠在一個方差更高的大型 (對於移動平臺來講) 數據庫基準測試的系統中運行納秒級精度的微型基準測試。
您也能夠本身嘗試!點擊運行按鈕,嘗試咱們 CI 中處理 WorkManager 基準測試產生的數據的算法。它將輸出兩個連接,一個指向帶有 測試迴歸 的 build ,另外一個指向 後續相關的修正 (點擊 "View Changes",來查看該次代碼提交的詳細內容) 。這些內容與人們在繪製數據時看到的迴歸和改進相匹配:
根據咱們對算法的配置,圖中的全部次要噪聲都將被忽略。當它開始運行時,您能夠嘗試用下面兩個參數控制算法:
增長寬度值會下降不一致性,可是也會致使在結果變更較爲頻繁時難以發現測試迴歸——咱們當前使用的寬度值是 5。閾值用於總體的敏感性控制——咱們當前用的是 25。下降閾值能夠看到捕捉更多的測試迴歸,可是也可能致使更多的誤報。
若是想在您本身的 CI 中進行配置,須要:
若是有迴歸或改進,請發出警報 (電子郵件、問題或任何對您有用的措施) 以檢查當前 WIDTH 所涵蓋的 Build 的性能。
那麼預提交又是什麼呢?若是不但願在 Build 中出現測試迴歸,則能夠經過預提交來捕捉迴歸。在提交前運行基準測試多是徹底防止迴歸的好方法,可是首先要記住: 基準測試就像 Flaky 測試同樣,須要像上述算法這樣的基礎結構來解決不穩定問題。
對於可能中斷提交補丁工做流的預提交測試,您須要對所使用的迴歸檢測有更高的可信度。
因爲單次運行基準測試並不能給咱們本身帶來足夠的信心,因此上面的分步擬合算法是必須的。一樣,咱們能夠經過獲取更多數據來增長這方面的信心——只須要不加修改地屢次運行,來檢測補丁是否引入了測試迴歸便可。
對於每次修改代碼而後進行的屢次基準測試,都會增長必定的資源消耗,若是您能夠接受,那麼預提交就可以很好地發揮做用。
全面披露——咱們目前沒有在 Jetpack 的預提交中使用基準測試,但若是您願意嘗試,如下是咱們的建議:
Jetpack Benchmark 提供了一種從 Android 設備外獲取準確性能指標的簡便方法。結合上面的逐步擬合算法,您能夠解決不穩定的問題,從而能夠在性能問題影響到用戶前發現它們的測試迴歸問題——就像咱們在 Jetpack CI 中作的同樣。
關於從何處開始的注意事項:
若是您想了解更多,請查閱 2019 Android Developer 峯會中咱們的演講:《在 CI 中使用 Benchmarks》
若是想更多瞭解 Jetpack Benchmark 是如何工做的,能夠查看咱們在 Google I/O 的演講:《使用 Benchmarks 提高應用性能》
咱們使用 Skia Perf 應用來跟蹤 AndroidX 庫的性能,基準測試結果能夠在 androidx-perf.skia.org 找到。因爲它如今在咱們的 CI 中運行,您能夠看到此處描述的逐步擬合算法的 實際來源。若是您想了解更多信息,Joe Gregorio 撰寫的另外一篇有關他們更高級的 K-means 聚類檢測算法的博文,解釋了 Skia 項目開發的特定問題和解決方案,這些問題和解決方案是專門爲整合多種配置 (不一樣的操做系統和操做系統版本,CPU/GPU 芯片/驅動程序變體,編譯器等) 設計的。