Google Dart精粹:應用構建,快照和隔離體

來源: InfoQ  發布時間: 2012-03-15 08:17  閱讀: 1278 次  推薦: 0   原文鏈接   [收藏]  

  英文原文:The Essence of Google Dart: Building Applications, Snapshots, Isolates

  (作者:Werner Schuster 譯者:曹如進)

  既然程序設計語言已經有了上千種,為什么 Google 還要推出 Google Dart 呢?它又會增加什么樣的特性呢?答案很簡單:Google Dart 團隊想要開發一門適合現代應用程序開發的語言,它既能在服務端工作,也能在(移動)客戶端工作。

  Dart 的一些特性解決了像 Java 或 Javascript 語言長久以來存在的問題。它的快照功能類似于 Smalltalk 的映像(image),使用快照不僅可以帶來(接近)即時的應用程序啟動速度,而且還沒有映像遺留的一些問題。隔離體特性可以確保代碼在無共享狀態的單線程內執行,它的消息傳遞并發類似于 Javascript 中的 WebWorker 和 Erlang 中的進程。Dart 的這些語言特性使得我們可以開發可擴展的和模塊化的應用。Dart 代碼既可以被 DartC 編譯器編譯成普通的 Javascript,也可以在 Dart 虛擬機(Dart VM)中執行。

  下面將介紹應用程序開發中使用 Dart 的樂趣所在,并重點介紹 Dart 虛擬機以及一些值得關注的語言特性。

  Dart 是一門應用程序語言:快照和初始化

  應用程序的啟動時間真得有那么重要嗎?用戶在每一天又會重啟集成開發環境(IDE)或者字處理器軟件多少次呢?隨著內存有限的移動設備的興起,應用程序啟動變得非常頻繁;移動設備操作系統中的內存不足(OOM)殺手進程顯得戰意十足,因為它毫不猶豫地殺死處于中止狀態的應用程序。iOS 中的多任務模型及其赫赫有名的物理 Home 按鈕,也同樣在縮短移動應用程序的平均壽命。在 iOS4 之前,按下 Home 按鈕總會殺死正在運行的應用程序;而在 iOS4 中,雖然情況變得稍顯復雜,但是應用程序仍然必須準備隨時“死”去,“死”在用戶手里或是 OOM 進程下。

  然而這樣的行為在移動操作系統上并不會長久。“突然終止”和“自動終止”是 OS X 最近版本中才推出的應用程序屬性,它們被用來聲明該應用程序可以處理在任何時候被殺死(例如,當可用內存非常低的時候)并重新啟動,且整個過程對用戶透明。

  啟動緩慢從 Java 1.0 開始就是 Java GUI 應用程序的頭疼問題。啟動一個大型的 Java 應用程序需要大量的工作:需要讀取、解析、加載和鏈接數以千計的類;在 Java 1.6 之前,這個過程包含了為方法生成堆棧圖以驗證字節碼。然而一旦類被加載,仍然需要初始化(靜態初始化等)。

  對于當下的 Java GUI 應用程序來說,僅僅顯示初始 GUI 就需要大量的工作。雖然這個問題一直影響著開發人員和用戶,但是 Java 6 中引入的閃屏 API (SplashScreen API)標志著該問題尚未得到解決。

  快照 vs Smalltalk 映像

  為了解決啟動緩慢的問題,Dart 使用了堆快照功能,它類似于 Smalltalk 的映像系統。所謂堆快照,其實就是遍歷應用程序堆并將所有的對象寫入文件。目前 Dart 的發布版本上有一個工具可以幫助激活 Dart 虛擬機、加載應用程序代碼并在調用 main 之前采集一份堆快照。隨后 Dart 虛擬機可以使用這個快照文件快速地加載應用程序。

  快照還可以用于對 Dart 虛擬機中的隔離體之間發送的對象圖進行序列化。

  在 Dart 的最初技術預覽版本中,似乎并沒有什么 API 可以用作初始化快照,盡管這很有必要。

  快照的技術細節

  Dart 團隊在快照格式上投入了很多努力。首先,快照需要能夠移動到不同的機器上工作,不管目標機器是 32 位,64位還是其他。同時,快照還需要能夠被快速地被讀進內存并盡量減少的額外工作,如指針修正。

  更多詳細信息,請參考 runtime/vm/snapshot.cc 和 runtime/vm/snapshot_test.cc 以了解快照系統的使用方法:導出完整快照,讀取快照,從快照啟動隔離體等等。

  快照 vs Smalltalk 映像

  Smalltalk 映像特性并沒有普及;Gilad Bracha 曾寫過一篇文章討論實踐中 Smalltalk 映像的問題。Smalltalk 開發通常發生在某個映像之中,其中的無用代碼會被剝離且映像會被凍結以進行部署。Dart 的快照與之不同,因為它們不僅是可選的,而且需要通過啟動應用程序并采集快照才可以生成。由于 Dart 缺少動態代碼評估以及其他的代碼加載特性,因此剝離過程變得更加徹底。

  Dart 的快照功能目前在 DartC 編譯后的 Javascript 的代碼中不受支持。

  快照也被用作隔離體間的消息傳遞;消息發送前在一端為對象使用SnapshotWriter進行序列化而后從另一端進行反序列化并讀取。

  不管怎樣,快照功能就在 Dart 虛擬機和工具中。同 Dart 的許多其他特性一樣,快照該怎么用要由社區說的算。

  最后我要說的是,Google 的 V8 Javascript 引擎中也有了快照的功能,它通過從快照中加載 Javascript 標準庫來改善啟動速度。

  初始化

  即使沒有快照,Dart 也被設計成盡量避免在啟動時進行初始化。Dart 中的類是聲明性的,也就是說創建它們并不需要執行什么代碼。庫可以定義final的頂級元素,即類之外的函數和變量,但需保證它們為編譯期常量(查看語言規范中的 10.1 節)。

  與 Java 中的靜態構造器,或是那些在啟動時依賴各種元編程方法生成數據結構、對象系統等語言相比,Dart 最優化了應用程序的啟動時間。

  Dart 目前并沒有反射機制,但是看起來基于 Mirrors (PDF)的實現會在不久的將來出現在語言中,通過它也許能夠使用 API 構建代碼并將其加載入新的隔離體中,從而將元編程帶進 Dart。

  并發單元,安全和應用:隔離體

  并發

  隔離體是 Dart 中的基本并發單元。每個隔離體是單線程的。為了能夠在后臺執行工作或是利用多核或多處理器,需要啟動新的隔離體。

  Google V8 最近同樣增加了隔離體特性,隔離體的加入為 V8 的內置程序提供了方便,通過在同一個系統進程中啟動多個隔離體可以實現成本更低的 Web Worker;這個特性暫未對 Javascript 開放。

  這種為并發而存在的多個獨立隔離體模型類似于 Javascript 和 Erlang。Node.js 也需要使用進程來利用多處理器或多核;許多管理 Node.js 進程的解決方案已經橫空出世。

  其他的單線程或綠色線程(Green Thread)語言都有類似的進程管理方案。Ruby 中的 Phusion Passenger 就是一個例子,它試圖解決在多個進程內加載相同代碼的開銷問題:Phusion Passenger 首先會加載 Rails 應用程序,并使用系統調用fork快速創建具有相同程序內容的多個進程,從而避免重復多次地解析和初始化相同的應用程序。Dart 的快照特性用另一種方式解決了這個問題。

  可靠性

  Dart 的首個技術預覽版本為每個隔離體使用一個線程,但是與此同時其他模型也正在考慮之中,如將多個隔離體多路傳輸到一個線程中或將隔離體運行在不同的系統進程中,這些做法同樣可以讓隔離體運行在不同的機器上。

  將應用程序分割成多個獨立的進程(或隔離體)有助于提供可靠性:如果某個隔離體崩潰,其他隔離體將不受影響,同時隔離體的干凈重啟也成為可能。Erlang 的監控樹(supervision trees)在這個模型中十分受用,體現在它能夠監控進程群的存活和死亡狀態并寫入自定義策略用來處理進程死亡。

  這篇 Akka 和 Erjang 創始人訪談 文章中對 Erlang 模型的優勢給出了一個很好的概述。

  安全性

  不受信任的代碼可以運行在自己的隔離體中。所有與之的通信必須通過消息傳遞發生,附加的能力-風格(capability-style)機制可以限制隔離體中誰可以與哪個端口進行會話。隔離體必須給定一個信息發送的端口,否則它什么也干不了。

  內存劃分

  將應用程序分割成隔離體還有一個好處:每個隔離體的堆都是獨立的;其中所有的對象都完全的屬于它自身,這不同于共享內存環境下的對象。其實關鍵的好處在于:如果為某個任務啟動了隔離體,那么在任務結束時整個隔離體可以一次性被回收而不需要運行垃圾回收器。

  此外,如果一個應用程序被分割成多個隔離體,那么就意味著該應用程序使用的內存被分割成更小的堆,即小于應用程序使用的內存總量。每個堆由它自己的垃圾回收器所管轄,這帶來的效果就是一個隔離體中的完整垃圾回收器只能工作在該隔離體之中而不會影響其他隔離體。 對那些易受垃圾回收器中斷(GC pauses)影響的 GUI 應用程序和服務器應用程序,一則好消息是:那些讓垃圾回收器一直忙個不停的糟糕隔離體將不會影響到易受時間影響的組件。因此,讓每個隔離體擁有各自的堆可以提高模塊化程度:每個隔離體控制自己的垃圾回收器中斷行為,且不會受到其他組件的影響。

  雖然 Java 和 .NET 中的垃圾回收器已經改善了許多,然而垃圾回收器中斷對于 GUI 應用程序以及時間敏感的服務器應用程序仍然是一個重要的問題。雖然類似 Azul 垃圾回收器的解決方案已經可以使中斷變得可控甚至幾乎消失,但是它們要么需要特殊的硬件,要么需要訪問系統基礎設施中的底層,如基于 x86 架構的 Zing實時垃圾回收器確實存在,盡管如此,它們還是犧牲了執行速度來換取可預測的中斷。

  將內存分割成單獨的堆意味著垃圾回收器的實現可以變得更加簡單且依舊足夠得快。當然,這些都取決于開發人員——想要受益于這些特性,一個應用程序就必須分割成多個隔離體。

  依賴注入不再必要:Dart 中的接口和工廠

  常常聽說應當“面向接口編程”,然而這么做在實踐中顯得有點困難,因為在調用時用戶不得不在new調用后跟上實際的類名。在 Java 世界里,這個問題導致了依賴注入(Dependency Injection, DI)框架的產生。采用依賴注入框架,就意味著首先要在項目中注入特定的 DI 框架依賴。

  那么依賴注入解決了什么問題?在特定類上調用 new 不僅需要硬編碼類,還會給測試以及代碼靈活性帶來問題。畢竟,如果所有的代碼都基于某個接口編寫的話,那么具體怎么實現將沒有關系,且用戶應當為使用案例選擇正確的實現方式。

  Dart 目前的版本中也有一種依賴注入的解決方案,使用它可以讓許多選項需要選擇的情況變得不再必要。Dart 通過在語言之中將接口鏈接至代碼來實例化對象。所有這一切需要的靈活性都隱藏在了工廠(Factory)之中,無論是決定實例化某個類,還是分配某個新的對象,抑或只是返回一個緩存對象。

  接口通過名稱來引用某個工廠,而工廠可以用庫的方式進行提供;工廠的不同實現可以存在于它們自己的庫當中,且由開發人員決定包含最好的實現。

  語言

  Google Dart 是一門新型語言,但它設計成讓多數開發人員看起來很熟悉。Dart 語言類似花括號語言,它提供面向對象編程(OOP)并重點關注接口。Dart 的面向對象編程系統有著類的概念,這與最近一些其他的語言不大一樣,如 Clojure(它使用協議(protocols)和類型完成面向對象編程)或者 Google Go 語言(Go 有接口,但是沒有類)。 語言內置面向對象編程特性的好處在于可以像 Javascript 一樣,擁有一套新的面向對象系統和范式約定的庫。

  詳細信息請參閱官方的 Dart 語言規范;如果想要快速了解下的話,可以查看 Dart 網站上的‘Dart 慣用法’(Idiomatic Dart)文章。

  模塊化

  Dart 中的命名空間采用了庫機制,它不同于 Java 中僅能使用類名來定義方法或變量命名空間。很重要的一點是:Dart 中的庫除了包含類之外,還可以包含頂級元素,即類之外的變量和函數。

  print函數正是一個例子,因為它是無類的頂級函數。庫系統也為名字沖突提供了一種解決方案:庫A可以導入另一個庫B,為了避免A和B間的命名沖突,所有從庫B中導入的名字都會被加上前綴,也就是說,使用#import ("foo.dart", "foo") 導入庫后,所有其中的可用元素都會擁有前綴"foo."。

  可選類型(Optional Typing)

  “可選類型”關鍵在于“可選”。開發人員可以為代碼加上類型標注,但這些標注又對代碼行為根本沒有影響。事實上,Dart 中還可以指定一個無意義類型——而同時代碼仍然能夠正常運行。

  在代碼中擁有類型可以讓各種類型檢查器各司其職。Dart 附帶的編輯器中擁有一種類型檢查器,它能夠高亮類型錯誤并將其當做警告。Dart 中還有一種檢查模式,在該模式中類型標注可用來檢查代碼,任何違規都會被報告成警告或錯誤。

  實際上可選類型標注在代碼中會具有類型信息,這些信息對編制文檔會有幫助;使用可選類型不再需要更多的文檔來解釋某個參數必須實現一個特定的方法才可以接受。接口的存在(即帶有方法簽名的方法名稱集合,以及可選類型標注)可以幫助文檔化 API。

  關鍵在于,語言總是動態的而且參數也可以被指定為動態的,也就是Dynamic類型。

  運行時可擴展性和可變性——暫缺

  讓我們看看這些:沒有猴子補丁(Monekypatching),沒有eval,目前沒有反射。雖然基于 Mirror 的系統正在準備階段(詳情請見介紹 Mirrorpaper 的論文),但是計劃里似乎限制了在新隔離體中加入新的代碼,因此這些還不在當前運作的進程當中。

  noSuchMethod

  Dart 的noSuchMethod特性,類似于 Ruby 的method_missing、Smalltalk 的 DNU 或其他一些語言中類似的特性。未來版本的 Javascript 也應當以動態代理(Dynamic Proxies)的形式提供類似的功能,這個功能正在緩慢的進入到 Javascript 虛擬機中(如 V8)。

  閉合類(Closed Classes),沒有 Eval

  像 Ruby 這樣的語言甚至可以允許在運行時對類進行修改,這些類被稱為開放類(open class)。然而不具備這種特性會對性能有所幫助:所有的成員在編譯期可見,這樣可以對代碼進行分析以移除沒有被引用過的部分。查看下面的‘批評’段落可以了解 Dart 中該特性的當前狀態以及其他語言中當前已有的解決方案。

  未來的語言特性

  一種 async/await 風格的擴展正在被考慮用作幫助編寫I/O代碼。Dart 中的多數I/O API 都是異步的,因此那些讓這項工作變得更加簡單的事情都是受歡迎的。不添加像協程(Coroutine)、纖程(Fiber)及其變種等特性的原因是為了避免增加同步的功能。 一旦協程存在于系統中,就有可能要安排和交錯它的執行,此外,想要編寫正確的代碼就有必要同步共享資源。因此,單線程會是重點;并發會則在隔離體中完成:顯式會話、無共享和隔離體等都會參與其中。

  批評

  沒什么能比一門新的編程語言更能惹惱開發人員了。下面讓我們快速地看下一些常見的批評。

  DartC 將 Dart 代碼編譯成大量的 Javascript 文件

  這個鏈接展示了一個 Dart 的“Hello Word”應用程序被編譯成了上千行的 Javascript 代碼。對此一個簡單的方案是:增加像 tree shaking 類似的優化,也就是刪除不被使用的函數。目前它已經在 Google Dart 和 DartC 團隊的待辦事項列表中。

  Dart 的某些特質使得這些優化變為可能,尤其是封閉類,這意味著所有的函數在編譯期均為已知。缺少eval就意味著編譯器知道編譯期間哪些函數被使用,更重要的是:知道哪些函數不被使用,從而可以把那些不被使用的函數安全地從輸出中移除。

  使用 Google Closure 工具的用戶可能知道其中的高級編譯功能。Closure 為 Javascript 帶來了一套類系統并允許使用信息標注類。在高級模式中,Closure 編譯器假定開發人員遵循了特定的規則,基于這個假定,如果某個函數在代碼中沒有被顯式的引用的話,就會被丟掉。顯然,如果程序員違反了這些規則,如使用eval等其他功能,那么代碼將不能工作。

  Google Dart 并不需要依賴于程序員遵守某些規則,語言本身的限制為編譯器提供了必要的保障。

  另外一個使用 Google Closure 高級編譯功能的例子是 ClojureScript 語言(這是一個帶'j'的 Clojure)。ClojureScript 注定也是一門應用程序語言,它沒有 eval 以及其他的動態代碼加載特性。它通過使用 Google Closure 的高級編譯工具編譯成 Javascript 代碼,以消除未被使用的庫函數。

  為什么不使用靜態類型對運行時進行優化?

  為什么類型是可選的?它既然存在,為什么又不用來提高生成的代碼?誠然,知道類型為int必然對優化生成的代碼有幫助。

  事實證明,Dart 背后的團隊知道這些想法,因為他們過去做出過一到兩個的虛擬機,Google V8 和 Oracle 的 Hotspot 就是兩個例子。

  出于若干原因,在 Dart 中使用靜態類型信息對于運行時代碼并不會有幫助。原因之一在于:開發人員指定的類型對語義毫無影響,事實上,他們 可能完全不正確。如果是這樣的話,雖然類型檢查器會發出警告,但是程序還是運行得很好。此外,由于給定的類型可能是無意義的,因此虛擬機并不能使用它們進行優化,因為它們并不可靠。也就是說,int相關的代碼可能僅僅是因為開發人員錯誤地進行了指定,如果實際運行時中的對象的真正類型是 String 的話。

  雖然靜態類型系統對工具和文檔會有幫助,但是它與執行的代碼并不相關。

  靜態類型沒法幫助生成優化的代碼還有一個原因:Dart 是基于接口的。如int中的操作符實際上是方法調用——接口上的方法調用。Dart 不是在開玩笑,int事實上是一個接口而非一個類。

  調用接口方法就意味著需要基于實際的對象及它的類信息在運行時進行加載。類似動態調用站點(CallSite)的內聯緩存(多態)的概念可以幫助消除方法查找的開銷。StrongTalk 和它的直接派生 HotSpot 根據優化后的反饋,以找出實際執行的代碼并生成優化后的代碼。V8最近同樣以 Crankshaft 的方式加入了這些優化。

  我最喜歡的語言特性在那里?

  Google 發布的 Dart 還處在一個相當早期的階段。用戶很容易被語言規范、集成開發環境、虛擬機、DartC 等搞混。Dart 團隊給出的明確消息是:現在可以嘗試一下 Dart,并提供一些反饋。Dart 的許多功能已經在計劃當中,但是有的尚未完成或者有的尚未開始;反射和混合類型已經提到會被當做潛在功能加入未來特性中。

  如果某個特性不在 Dart 的存儲庫或語言規范中,現在可以提供一些反饋意見并給出針對語言和運行時環境的修復或改動的建議。

  結束語

  Dart 的許多工作已經完成:語言規范、Dart 虛擬機、能夠將 Dart 代碼編譯成 Javascript 的 DartC 編譯器、基于 SWT 和 Eclipse 綁定包的編輯器等等。

  然而,最初發布的 Dart 只是一個技術預覽,語言、API 和工具包相關的工作還在進行中。現在可以給 Dart 團隊一些反饋,而且有機會能夠影響到該語言。語言將會發生改變,包括本文中提及的一些推薦和計劃的改動。

  有些人已經開始嘗試 Dart,例如 Dart 的 Java 遷移版本 JDart 項目已經開始了,它大量使用了 Java 7 中的invokedynamic功能。

  在本文中,我們重點關注了語言以及 Dart 虛擬機特性。使用 DartC 可以將 Dart 代碼編譯成普通的 Javascript 文件;簡介部分展示的一些樣例,包括那個能夠運行在 iPad 上的例子,實際上是 Dart 應用被編譯成 Javascript 之后運行在標準瀏覽器中。

  雖然 Google Dart 最初的開發是秘密完成的,但是整個項目、源碼、工具及簽單系統等等現在都是對外開放的。至于 Dart 能夠用在何處仍然有待觀察。正如前面所述,Dart 虛擬機提供的功能使得 Dart 對客戶端開發人員以及服務端開發人員都頗具吸引力。

  鏈接:

  關于作者

  Werner Schuster (murphee) 時而編寫軟件,時而撰寫軟件相關的文章。

  查看英文原文:The Essence of Google Dart: Building Applications, Snapshots, Isolates

0
0
 
標簽:Dart
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()