【譯】Profiling Flutter Applications Using the Timeline

  • 2020 年 2 月 26 日
  • 筆記

使用Timeline可以查找和解決應用程式中的特定性能問題。它也是一個很好的工具,可以識別出Flutter所提供的所有特性的相對性能成本,並允許您做出更明智的決定,確定哪些地方需要避免某些特性,哪些地方需要使用可能會讓您的應用程式脫穎而出的效果 .

The Timeline

Flutter 提供的一個開箱即用的性能分析工具去記錄Dart Timeline的軌跡。Timeline 工具讓您能夠詢問和回答為什麼您的應用程式可能會janking的具體原因。作為經常被指派在不熟悉的程式碼庫中查看性能問題的人員,使用Timeline工具進行概要分析和很輕鬆,壓根不需要你對程式碼又多了解。因此,Timeline通常是我個人用來診斷和修復性能問題的第一個也是最有用的工具。對於是Flutter本身的性能瓶頸,請直接提交一個問題,其中,你需要提供包必要的測試用例和Timeline trace,來幫助Flutter開發人員更容易識別(交叉引用和優化特定任務),已便進一步提升Flutter引擎和框架的性能。我必須承認,對於同樣優先順序的問題,我將首先選擇帶有附加Timeline trace的問題。

Timeline是幹啥的

時間軸是一個環形緩衝區,記錄應用程式程式碼在其運行過程中記錄的事件。要記錄的事件類型及其記錄頻率由發出事件的子系統的作者確定的與性能可能相關的內容決定。.

要使用 Timeline, 請遵循:

  • 啟動和停止記錄TimeLine事件的能力。
  • 能夠將TimeLine事件導出為可移植格式並在離線狀態下查看.
  • 使用程式碼發送 Timeline 事件.

Timeline Trace 事件格式

記錄在循環緩衝區中的事件是非常輕量級的。要以可診斷的形式實際查看這些事件,必須將其導出為適當的可移植格式。Trace Event Format被Flutter用來導出這些時間軸事件,以便在專用的跟蹤查看器中查看。這和Catapult開發的性能概要收集、顯示和分析家族工具有著相同的格式查看器.

跟蹤事件格式和查看器,並被許多其他項目使用。這些項目包括ChromiumAndroid(通過systrace)。事實上,它是如此有用,Chromium已經內置。可以嘗試在基於chrome的瀏覽器中導航到chrome://tracing.

您與其他開發人員共享的Traces是JSON文件或其tarball。直到最近我才知道,trace viewer可以自動解壓縮JSON文件tarball。.

Flutter 渲染幀相關背景知識

在我們嘗試識別潛在的性能問題之前,我們需要對一個健康(大概是指基本無性能問題)的Flutter應用程式有一些了解。本節是一個關於Flutter如何渲染幀的一個快速介紹.

執行緒

當Flutter應用啟動時,它又啟動(或從池中挑選)三個執行緒,這些執行緒有時有重疊的區域, 但大體上講,它們被稱為UI執行緒GPU執行緒IO執行緒. 這裡需要注意的是UI執行緒和原生如Android平台的UI執行緒(主執行緒)並不是一回事,通常Android平台上稱UI執行緒為主執行緒,然而,在Flutter中我們要注意,你眼裡的主執行緒其實在Flutter這裡是Platform thread.

Flutter Engine裡面的執行緒

UI執行緒是所有代表框架執行的Dart程式碼和應用程式運行的執行緒。 Unless your application or the framework (via methods like foundation::compute) launches its own isolates (via methods like Isolate.spawn), Dart code will never run on any other thread

一般來說dart程式碼將永遠運行在UI執行緒,絕對不會跑到其他執行緒上除非你的應用程式或框架通過以下兩種方式啟動專屬的isolates

  1. foundation::compute
  2. Isolate.spawn

而且要記住的是:即便啟動一個新的 isolate , Dart code 也永遠不可能會在 GPU執行緒 或者 IO 執行緒上執行。

平台執行緒是所有依賴插件的程式碼運行的地方。這個執行緒也是平台的本地框架為它們的任務提供服務的地方。Flutter應用程式以一種非同步的方式與它們的插件進行交互,並且插件不應該去夠阻塞任何由Flutter管理的執行緒.

除了上述四個執行緒之外,Dart VM還管理一個執行緒池。這個執行緒池用於服務多種功能,如等待socket for dart:io、垃圾收集和JIT程式碼生成(僅在debug模式下,我們知道Flutter在release模式下使用AOT,所以release模式是沒有JIT程式碼生成執行緒【譯者注】)。.

The Frame Pipeline[管線]

Frame Pipeline

為了產生一個單一的幀,Flutter引擎首先裝備一個vsync鎖存器。vsync事件指示Flutter引擎開始工作,最終在螢幕上呈現新幀。平台生成的vsync事件考慮了顯示的刷新率(以及其他因素),也就是說,平台生成vsync事件也有策略的,掉幀的化,vsync事件的生成應該會受影響【譯者注】。

vsync事件喚醒UI執行緒。還記得嗎,UI執行緒是Dart程式碼運行的地方。UI執行緒上的所有操作的結果是一個layer tree,他將交給後端(OpenGL、Vulkan或Software)去程式到螢幕上。

一旦layer tree層樹被創建,GPU執行緒被喚醒並開始轉換layer tree到一個GPU命令緩衝區。然後這個命令緩衝區被提交給同一執行緒上的GPU.

對於足夠複雜的場景,UI執行緒可以並發地生成下一幀,因為GPU執行緒正在消耗前一幀的layer tree。將一個UI和GPU執行緒上串且完成看成一個單元的話,這個單元就叫pipeline Item。pipeline深度是引擎在任何給定時間所處理的幀工作負載的數量。管道深度可能不同.

為什麼會掉幀

特定的操作模式,掉幀現象可以在一個Flutter應用程式中被感知,比如滿足下列條件之一:

  • pipeline item的UI或GPU執行緒組件超出幀預算(對於60Hz的顯示刷化率,通常為16.67 ms)意思就是UI執行緒和GPU執行緒執行一幀執行時間都不要超過16.6666ms,超過了就會掉幀【譯者注】.
  • 引擎改變管道的深度
  • 來自平台的vsync事件以不一致的速率發出或掛接.

這個列表中一個值得注意的例外是,引擎有選擇地以一致的速度忽略vsync事件。例如,在60Hz的顯示器上,如果引擎僅在其他vsync脈衝上的管道項上開始工作,那麼Flutter應用程式將呈現一致的30Hz.

掉幀(因為著色器編譯)

如何收集 & 解讀 Timeline Traces

有了對以上掉幀相關的了解,現在我們可以自己搜集Timeline Traces了。

跟蹤收集開銷相當低,但對性能也有些許影響!因此,Flutter引擎僅在debugprofile模式中收集跟蹤。profile模式與用戶在運行應用程式時所期望的性能最為相似。此模式使用AOT編譯您的Dart程式碼,與release模式類似。然而,它也支援時間軸,以及通過Dart observatory站提供的其他跟蹤工具.

profile模式構建所需的時間與release模式相同,意思是會比debu耗時長點【譯者注】。但是,不要因此而放棄使用profile模式。我通常喜歡在debug模式下向時間軸添加跟蹤(主要是有HotReload)。然後,當我合理地確信我的跟蹤將收集我需要的資訊時,我在profile模式中執行一個構建來收集有效的timing information.

要收集 traces,你需要打開 Dart Observatory , Observatory 是一個 web應用 application 服務端,主要你的flutter應用已debug,或profile模式跑起來,都會起一個服務,並且隨機開一個埠,Flutter工具轉發此埠到您的PC機器上。.

注意: Observatory 將會被 Dart DevTools 取代. 目前是preview版本. 參考資料 DevTools』 docs and, using the timeline view.

命令行開啟 Observatory

  • 使用 flutter run — profile (或者 debug)啟動應用。
  • 應用跑起來,將會在命令行看到類似這樣的: An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:60551/
  • 直接點擊就可以打開了

通過IDE打開Observatory

Observatory導航到 Timeline

Either click on the timeline link on the main observatory page or navigate to the timeline directly by opening http://127.0.0.1:61102/#/timeline (replace the # with the actual port number)

點擊main observatory主頁上的時間軸鏈接,或者打開http://127.0.0.1:61102/#/timeline 直接進入時間軸(用實際埠號替換時間軸).

細心的人會發現,如果想指定埠,可以使用, — observatory-port=<port> . 輸入 flutter help run 可以看更多細節.

點擊之後,就看到.

搜集 Trace

通過點擊all啟用所有跟蹤類別,一般勾選Flutter developer,然後點擊clear按鈕開始跟蹤設備。請注意,「all」都已啟用但時間軸仍然是空的,但設備已經開始收集痕迹。確保以向時間軸添加跟蹤的方式與應用程式交互。通常,渲染一些幀是可行的

點擊 Refresh 按鈕, Observatory 會將 current trace buffer 從設備拉取過來。

A Freshly Collected Trace (enable Flow & VSync events)

溫馨提示:當不知道怎麼操作的時候,多看看右上角的?按鈕.

保存 & 分享 Traces

單擊save按鈕將使瀏覽器下載包含跟蹤的JSON文件。您可以在bug報告或電子郵件中共享跟蹤。要查看共享跟蹤,請在Chrome中導航到about://tracing,並載入之前保存的跟蹤文件。

Chrome中的跟蹤查看器about://tracing也可以處理壓縮的JSON文件。我發現這些更容易分享和工作。

Elements of a Trace

持續時間事件

引擎中最常用的跟蹤事件類型是持續時間事件。這樣的事件允許您在跟蹤中注釋程式碼塊。因為它們不需要標識符,所以添加它們非常簡單。在Dart中,您可以使用 dart:developer package』s Timeline 類來自己添加跟蹤。Flutter engine & framework已經將持續時間事件添加到它認為重要的工作負載中。你也可以這樣做。點擊一個特定的持續時間,你就會看到花在該事件上的時間摘要。

Timeline.timeSync("Doing Something", () {      doSomething();   }  );

查找UI或GPU執行緒上的持續時間事件,它們是幀間隔的重要塊。可以通過啟用前面描述的Highlight Vsync按鈕或者直接按v鍵來突出顯示幀間隔。

如果您看到一個特別大的持續時間事件,下一步是突出顯示程式碼的哪一部分對該塊有貢獻。當使用下面描述的取樣分析器時,識別這樣的塊要容易得多。但是,如果您對所討論的程式碼庫有一定的了解,您也可以推測地向程式碼中添加跟蹤。雖然只有在profile模式下才能收集到可靠的儀錶號,但是我喜歡在debug模式下使用熱重載來推測性地添加跟蹤,以查看我是否離隔離瓶頸越來越近了.

Event summary

單擊事件將在底部的窗格中顯示事件摘要。摘要的Events部分特別有用,因為它嘗試連接所有邏輯上相關的持續時間事件。這些關係是使用下面描述的流事件推斷出來的。Flutter框架和引擎已經為所有框架相關的工作負載添加了流事件。通過這種方式,您可以更容易地隔離與特定框架相關的所有工作(跨多個執行緒)。當您單擊相關流的鏈接時,跟蹤查看器將選擇並突出顯示所有連接的流。

在下面的示例中,選擇所有相關的跟蹤並按Self Time對列表排序,表明PhysicalShapeLayer::Paint trace是主導跟蹤。可以看出這個跟蹤是在GPU執行緒上,因為在摘要中對相同的圖形進行滑鼠拖動會突出顯示相同的圖形

一旦確定了這些主要的跟蹤,我通常就知道應該深入研究程式碼的哪些部分。摘要也很有用,因為在跟蹤過程中很容易在視覺上錯過多次持續時間極短的小事件。但是這樣的痕迹會在摘要中立即突出顯示

Repeating Events

有時,您需要描述生成持續時間跟蹤的重複事件的性能,而不是單個事件。這可以通過拖動選擇包含此類事件的時間軸部分來實現

trace viewerCatapult,可能有不同版本的Catapult嵌入在Chrome的about://tracing 和Observatory。我使用最新版本的Chrome瀏覽器,因為它有更多的最新跟蹤查看功能。你也可以直接在GitHub上使用Catapult。如果您想查看在最新版本的Catapult中在Observatory中收集的跟蹤,請將跟蹤保存為JSON並在不同版本的Catapult中載入相同的跟蹤。JSON格式是穩定的。

一旦您選擇了一組跟蹤,您應該會看到該選擇中重複的所有跟蹤的摘要。選擇一個跟蹤(在下面的例子中我使用的是最新版本的Catapult跟蹤查看器),應該會顯示有用的資訊,比如每片的平均時間、標準偏差、重複計數等。

這將使您更好地了解您對程式碼庫所做的改進,這些改進反映在重複事件(如幀)中較小的持續時間事件中。此外,一旦您確定了異常值,您就可以更容易地選擇相同的異常值,並分析圍繞相同異常值的跟蹤,以解釋偏差。

Flow Events

流事件用於邏輯地連接持續時間事件(可能在多個執行緒上)。在跟蹤查看器中,它們顯示為箭頭。默認情況下,流事件會使跟蹤視圖非常混亂,並且被禁用。要啟用相同的功能,請選中「View Options」中的「Flow events」框。流事件必須起源於持續時間事件。使用 Flow class in the dart:developer 包中Flow類可以自動完成這項工作。Flutter引擎還將一個稱為「PipelineItem」的隱式流添加到所有幀相關的工作負載中。在上面描述的事件摘要中,所有與流計數相關的持續時間事件都是「相關的」。關聯事件的後代也被計算在內。

在下面的例子中,在GPU執行緒渲染前,Flutter引擎正在UI執行緒上生成下一幀。如果沒有流,就很難將持續時間事件與特定的框架工作負載關聯起來。

The Sampling Profiler

通常,在添加跟蹤時很難確定從哪裡開始。每當引擎喚醒一個執行緒進行工作時,它都會添加一個名為MessageLoop::RunExpiredTasks的隱式持續時間跟蹤。但是,在相同的跟蹤中可能不會嵌套更多的跟蹤。在這種情況下,取樣分析器非常方便。Dart VM以特定的頻率收集當前程式碼的回溯。無論何時運行任何Dart程式碼,這些示例都將在UI執行緒上顯示為即時事件

samples很容易丟失,但非常方便。選擇一個samples會在那個時間點顯示backtrace和本機堆棧的回溯軌跡。如果您迷路了,不知道從哪裡開始添加跟蹤,只要繼續選擇一個示例,直到您識別出看起來很熟悉的程式碼。

這是一個簡短的概述,您可以使用timeline工具提升您的應用程式。祝旅途快,現在才發現我開車,而且車速很快?

原文鏈接