【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】
Dart 與可靠類型帶來的效能優勢

在過去的幾年中,我們強化了 Dart 的類型系統。最初的 Dart 語言(Dart 1)有一個不可靠的、可選的類型系統(類似於類型的 JavaScript 方言,例如微軟的 TypeScript 或 Facebook 的 Flow)。Dart 2 引入了一個更嚴格的、可靠的類型系統。在過去的兩年中,我們一直在努力透過 可靠的空安全 進一步擴展類型系統。
雖然可靠的類型系統為開發人員提供了更大的信心,但它也使我們的編譯器能夠安全地使用類型來優化生成的代碼。有了可靠性,我們的工具透過結合靜態和(必要時)運行時檢查來保證類型的正確性。沒有可靠性,類型檢查只能進行到一定程度,並且靜態類型在運行時可能不正確。
在實踐中,可靠性允許我們的編譯器生成更小、更快的代碼,尤其是在提前 (AOT) 設定中,我們將預編譯的原生代碼傳送給客戶端。
範例
以下範例方法演示了可靠類型如何對相對簡單的代碼產生巨大影響:
1 | int getAge(Animal a) { |
在我們最後一個穩定的 Dart 1 版本 (1.24.3) 中,這個方法映射到 26 個原生 x64 指令——這還是在檢測和設定檔引導的優化之後,這會減慢初始運行時啟動速度。在 Dart 2.12 中使用可靠的空安全,此代碼僅映射到 3 個指令,而無需任何設定檔引導的優化。
Dart 編譯為 ARM32/64 和 x86/x64 架構。在以下範例中,我們使用 x64,但在其他目標上的結果類似。
範例方法的完整 Dart 代碼和上下文顯示在本文末尾,但以下是重點:
Animal
類包含一個類型為int
的欄位age
。Animal
有幾個子類別(Cat
、Dog
、Snake
、Hamster
)。- 上述方法在運行時在許多這些類型上被調用。
Dart 物件佈局
當編譯為原生 (x64) 代碼時,Dart 類 Animal
有一個簡單的佈局:

前 8 個位元組是一個標題,提供具體化的類型資訊(即物件的運行時類型)。第二個 8 個位元組包含 age
欄位。所有子類別都保留(並可能添加到)此結構:任何額外的欄位都佈局在後面,保留基本類型的結構。給定 Animal
(或任何子類別)的實例,getAge
方法應從 8 位元組偏移量載入欄位並返回它。
Dart 1:不可靠的類型
然而,在 Dart 1 中,靜態類型並不可靠,並且在編譯期間實際上被忽略。在運行時,我們不能假設靜態類型是正確的(因此,佈局也是預期的)。對 age
的存取可能是對不同偏移量處的欄位、對觸發更多可執行代碼的 getter 或對不存在的欄位(觸發可捕獲的運行時錯誤)的存取。
Dart 1 被設計為依賴於客戶端設備上的即時編譯器和虛擬機器,該編譯器使用運行時類型資訊來優化代碼。在這種方案中,我們實際上編譯了每個方法兩次:第一次是為了收集資訊,第二次(對於熱點方法)是根據觀察到的運行時行為生成更優化的代碼。
Dart 1:第一次編譯
getAge
的第一次編譯在 x64 上產生了以下 47 個指令:

請注意,此代碼已檢測以確定運行時會發生什麼。它對傳遞的物件沒有任何假設,並且有效地執行等效於雜湊表查找以正確找到欄位、執行 getter 或拋出錯誤。
Dart 1:第二次編譯
在這種情況下,代碼會被重複調用,並觸發第二次優化編譯,生成以下 26 個指令:

這個優化的代碼仍然很大。它基於設定檔資訊,發現該方法僅在 Cat
、Hamster
和 Dog
的實例上被調用,並根據未來也將如此的假設進行了優化。
藍色 代碼是方法的序言和結尾(用於設置和恢復堆疊框架)。紅色 代碼檢查預期的情況——實例非空且屬於先前看到的類型之一——並為其他情況調用慢路徑。粗體 代碼是載入欄位的實際工作。
如果未來的行為與過去不同,優化的代碼實際上可能會更慢:如果在新的實例(例如 Snake
)上調用 getAge
,代碼將執行額外的檢查,但仍然會進入慢路徑。
Dart 1 生成的代碼的問題
上面的生成的代碼在結構上與 Chrome 中的 JavaScript 引擎 V8 在給定或多或少等效的 JavaScript/TypeScript/Flow 程序時生成的代碼非常相似。雖然這種方法(和相應的生成的代碼)可以在許多情況下提供良好的效能,但當我們開始(尤其是使用 Flutter)面向更廣泛的客戶端平台(包括對大小和記憶體佔用更敏感的行動設備)時,它就不再適合了:
- 首先,客戶端編譯的成本增加了 Dart 應用程式的整體佔用空間。
- 其次,兩階段推測編譯的成本對應用程式啟動不利。
- 第三,iOS 上不允許即時編譯:我們至少需要針對某些目標的替代策略。
我們轉而採用提前編譯方法,但使用 Dart 1 會導致代碼質量差很多。即使使用複雜的、全程序分析,我們也無法始終在編譯時確定類型資訊,尤其是在應用程式變得更大時。此外,當整個應用程式都被預編譯時,推測的成本(上面的紅色代碼)變得過高。
Dart 2:可靠的類型
在 Dart 2 中,我們引入了可靠性,這使我們能夠安全地根據類型資訊編譯代碼,並減少了對設定檔以獲得效能的依賴。使用 Dart 2,在單個提前編譯上,我們在 x64 上生成 10 個指令:

此代碼仍然執行空檢查(紅色),如果發現空則調用輔助方法。
Dart 2.12:可靠的空安全
有了可靠的空安全,類型系統更加豐富,我們的編譯器可以利用這一點。編譯器可以安全地依賴於(現在的)非空類型,並消除上面的紅色代碼。在 Dart 2.12 beta 中,我們減少生成了 3 個指令:

事實上,隨著代碼變得更簡單,我們也能够簡化序言和結尾。在我們即將發布的穩定版本中,我們將只為範例方法生成 3 個指令:

有了可靠的空安全,我們可以將此方法的生成的代碼減少到其本質:欄位載入。在實踐中,對此方法的調用將始終被內聯,因為編譯器現在可以輕鬆地看到內聯是效能和代碼大小的雙贏。不再需要運行時檢查和補償代碼:更多的繁重工作在編譯時完成。我們不再需要客戶端編譯的啟動和記憶體開銷。因此,我們的用戶可以獲得更小、更快的代碼。
試試看!
我們鼓勵您嘗試 空安全。它在 Dart 2.12 中可用,現在在我們的 beta 頻道中。一旦您的上游相依項被遷移,您就可以遷移您自己的套件和應用程式。正如這裡的範例所示,您可能不需要做太多更改。
請記住,要獲得空安全的效能優勢,您需要一個完全遷移的應用程式。一旦您的應用程式完全遷移,我們的編譯器將自動利用空安全來生成更好、更小的代碼。
附註:代碼
這是完整的 Dart 代碼,我編譯它生成了本文中的所有代碼。雖然這裡的範例是人為設計的,但模式(類別層次結構中的欄位)相當常見。
Dart 與可靠類型帶來的效能優勢 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。