[譯] Performance testing of Flutter apps

Vadaski 發表於 2019-08-09

原文連結:Performance testing of Flutter apps 作者:Filip Hracek (from Flutter team)

Flutter 在預設狀態下就能執行得非常快,這點非常棒! 但是否這就意味著你完全不需要考慮效能呢?

答案是否定的,編寫一個速度非常慢的 Flutter 應用是完全可能的。然而,另一方面你也可以充分利用這個框架,讓你的 app 不僅快速,高效,而且使用更少的 CPU 時間和電量。

[譯] Performance testing of Flutter apps

這就是我們想要的!在一個有意義的指標上來比較兩個版本的應用在統計上明顯的差異

在 Flutter 中有一些效能優化的通用指導原則:

  • 更新狀態時,影響範圍儘可能地少。
  • 僅當必要時才更新狀態。
  • 不要在 build 方法中進行密集型計算任務,理想的話,把這些操作放在 main isolate 之外。

你可能很難相信,對於大多數效能優化的問題來說,它們的答案統統指向了這句話——“它究竟取決於什麼?”。對於特定 Widget 是否值得進行特定優化,並付出維護成本?在特定情況下的特殊處理是否合理?

對於這些問題唯一有用答案是測試和測量。量化每個選擇對效能的影響,並根據該資料做出決定。

好訊息是 Flutter 提供了出色的效能分析工具,例如包含 Flutter Inspector 的 Dart DevTools(目前還處於預覽版),或是 Android Studio 中的 Flutter Inspector(安裝了 Flutter 外掛下)。你可以使用 Flutter Driver 操作應用,並在 Profile 模式下儲存效能資訊。

然而壞訊息是,現在的手機實在是太過智慧了。😂

管理器的問題(Governors)

系統級守護程式需要根據當前負載調整 CPU 和 GPU 單元的速度,但是 iOS 和 Android 的管理器卻很難量化 Flutter 應用的效能。總的來說這還是一件好事,因為它確保了平穩的效能,同時也消耗盡可能少的電量。

然而缺點是你完全可以通過提高其效率以顯著加快應用的執行速度。

下面的例子中,你可以看到如何在應用中迴圈列印一些無意義的 print 語句,使得管理器切換到更高的檔位,從而讓應用執行更快速,效能更加可預測。

[譯] Performance testing of Flutter apps

管理器的問題:在預設情況下,你無法相信這些數字。在上面這個盒子圖中,我們在 x 軸上進行單獨執行(用它們開始的確切時間標記),並在 y 軸上表示構建時間。正如你所看到那樣,當我們引入一些完全不必要的列印語句時,它竟然會縮短(而不是增加) build 時間。

在這個實驗中(上圖),更差的程式碼反而導致了更快的構建時間,更快的光柵化時間和更高的幀率。如果客觀上更差的程式碼會得到更好的效能,那麼你就不能遵循這些指標。

上面僅僅是為了解釋為何移動應用效能基準不直觀,以及測試困難的一個例子。

接下來,我將分享一些 Google I/O app 上 Flutter 的例子,Developer Quest

基本建議

  • 不要在 DEBUG 模式下測量效能。只有 profile 模式下才能測量其效能。
  • 在真機上測試,不要在 Android 或者 iOS 模擬器上測試。雖然模擬器軟體非常適合開發使用,但是它們在效能表現上和真機差異非常大。Flutter 不允許在模擬器上以 profile 模式執行,因為這並沒有任何意義。這種方式收集的效能資料並不是實際的效能。
  • 理想的情況下是使用相同物理裝置。讓它作為你專用的效能測試機,並不再用於其他用途。
  • 學習 Flutter 效能分析工具

CPU / GPU 管理器

正如我們剛才所說的,現代作業系統根據負載和一些其他的啟發式調整每個 CPU 和 GPU 的頻率。(例如,觸控螢幕通常會讓 Android 手機將其優先順序置於更高的檔位。)

在 Android 上你能夠直接關掉這些管理器,我們稱之為“scale locking”。

  • 編寫一個指令碼來 scale-lock 你的測試機效能。你可以在 Skia’s recipe 中找到靈感,也可以查閱 Unix CPU API
  • 除非你正在執行像 Skia 這樣的大型基準測試,通常情況下你可能想要一些更加輕量,不那麼通用的東西。來看看 Developer Quest 中針對某些方面的 shell 指令碼吧。 例如,下面這個將 CPU 管理器設為 userspace (唯一一個不會自動調整 CPU 頻率的管理器)。
#!/usr/bin/env bash
GOV="userspace"
echo "Setting CPU governor to: ${GOV}"
adb shell "echo ${GOV} > /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"
ACTUAL_GOV=`adb shell "cat /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"`
echo "- result: ${ACTUAL_GOV}"
複製程式碼
  • 現在的目標並不是要模擬真實效能(沒有使用者 scale-lock 的裝置),而是獲取執行時可比較的效能指標。
  • 最後你需要進行測試,並在裝置上執行 shell 指令碼。只有這樣才是有效的,在這之前的效能資料都在欺騙你。

使用 Flutter Driver 我的桌面上執行 Developer Quest 的早期版本

Flutter Driver

Flutter Driver 能夠自動執行應用。你可以閱讀 flutter.dev 上效能分析的文章,瞭解如何使用它來分析應用。

  • 不要在效能測試時手動操作你的應用。始終使用 Flutter Driver 確保你的比較具有意義。
  • 編寫你的 Flutter Driver 程式碼,以便能夠讓其執行你真正想要測試的內容。如果你正在進行常規程式效能測試,請嘗試遍歷全部應用,並執行使用者會做的操作。
  • 如果你的應用具有偶然性(隨機網路事件等),請使用 mock 資料,並且保證它們儘可能相似。
  • 如果需要的話,還可以使用 Timeline 的 startSync() 方法 和 finishSync() 方法,來新增自定義時間軸事件。例如,當你想要測試特定方法的效能時,這很有用。將 startSync() 放在它的開頭,並在方法結束時使用 finishSync()
  • 對於各個不同版本的應用,需要進行多次測試。在 Developer Quest 時我彙總了 100 次。當你測量一些比如像“第 99 位百分數”這樣繁雜的東西時,你得執行更多次才行。而對於基於 POSIX 的系統來說,只需要執行下面這樣:
for i in {1..100}; do flutter drive --target=test_driver/perf.dart --profile; done
複製程式碼

[譯] Performance testing of Flutter apps

時間軸(Timeline)

時間軸是你執行 profile 模式下輸出的原始資料。Flutter 將此資訊轉儲到可被 chrome://tracing 載入的 JSON 檔案中。

  • 瞭解如何在 Chrome 的 tracing timeline 中開啟完整的 timeline。你只需要開啟 Chrome 瀏覽器的 chrome://tracing,然後點選“Load”,選擇那個 JSON 檔案。你可以在這篇 簡短指導 中獲得關於它的更多資訊。(同樣有 Flutter Timeline tooling,但目前還處於 tech preview 階段。因為在 Flutter 的 timeline tooling 準備就緒之前,Developer Quest 專案就已經開始了,所以我還沒用過那個。)
  • 使用 WSAD 鍵在 chrome://tracing 的 timeline 中移動,以及使用 1234 來更改操作模式。
  • 首次設定效能測試時,考慮是否使用 Flutter Driver 執行完整的 Android systrace。這使您可以更深入地瞭解裝置中實際發生的情況,包括 CPU 縮放資訊。但是,不要使用完全開啟的 systrace 測量您的應用程式,因為它會讓一切變得非常慢而且更加不可預測。
  • 那麼,應該如何使用 Flutter Driver 執行完整的 Android systrace 呢?首先,你得從 /path/to/your/android/sdk/platform-tools/systrace/systrace.py --atrace-categories=gfx,input,view,webview,wm,am,sm,audio,video,camera,hal,app,res,dalvik,rs,bionic,power,pm,ss,database,network,adb,pdx,sched,irq,freq,idle,disk,load,workq,memreclaim,regulators,binder_driver,binder_lock 開啟 Android systrace。 然後通過 flutter run test_driver/perf.dart --profile --trace-systrace 執行 app。最後,通過 flutter drive --driver=test_driver/perf_test.dart --use-existing-app=http://127.0.0.1:NNNNN/ 啟動 Flutter Driver(其中 NNNNN 是 Flutter run 上執行的埠給你的)。

度量

最好能看盡可能多的指標,但我發現一些相比其他更有用的指標。

  • build 的時間光柵化的時間 僅在真正嚴格的效能測試中才有用(預設情況下提供的度量標準是 TimelineSummary),這些測試除了 UI 之外不會包含太多其他內容。

  • 不要把 TimelineSummary.frameCount 看作計算每秒幀數(FPS)的方法。Flutter 的配置檔案工具不能給你提供真實的幀速率資訊。TimelineSummary 雖然提供了 countFrames() 方法,但它只會計算已完成的幀構建了多少次。一個優化良好的應用可以限制不必要的重建,每秒的幀數比經常重建的未經優化的應用程式少很多。

  • 我個人獲得的最有用的資料是通過測量執行 Dart 程式碼所花費的總 CPU 時間得到的。這會算上 build 方法和外部執行的程式碼。假設你在 scale-locked 的裝置上執行配置檔案測試,總 CPU 時間可以很好地估算你的應用程式將消耗更多還是更少的電。

[譯] Performance testing of Flutter apps

  • 要找出 Dart 程式碼執行所花費的總 CPU 時間,最簡單的方法是看 timeline 中 MessageLoop:FlushTasks 事件的範圍。在製作 Developer Quest 的時候,我編寫了一個 Dart 工具來提取它們。
  • 要檢測 jank (即跳過的幀),請尋找極端情況。例如,對於 Developer Quest 的特定情況以及我們用於測試的裝置來說,觀察 95% 的 build 時間很有幫助。(90% 的 build 時間都很相似,即使是在比較效率水平差距非常大的程式碼也是一樣,而 99% 往往又過於亂了。但你的情況可能會有所不同。)

[譯] Performance testing of Flutter apps

  • 正如我之前所提到的那樣,對於你的每個版本的應用都演算它上百次, 然後使用有些許誤差的平均值或百分位資料。如果使用箱形圖就更好了!

結論

當這些都設定好了之後,你就能夠自信地比較提交和實驗。下面,你可以看到一個很常見困境的答案:“這種優化值得維護開銷嗎?”

[譯] Performance testing of Flutter apps

我認為對於這個情況來說,答案是肯定的。只需幾行程式碼,我們應用的每個自動測試平均可以減少 12% 的 CPU 時間。

然而,本文的主要內容是,不同的測量方式可能會反映處非常不同的東西。嘗試過於寬泛地推斷效能測量也許十分符合直覺,但卻是錯誤的。

換句話說:“它究竟取決於什麼”。我們應該信奉這句話。

以上便是翻譯的全部內容,我認為這篇文章對我們構建高效的 Flutter 應用具有很大的指導意義,所以便翻譯出來分享給大家。

如若有譯誤還請指出。