如何評論瀏覽器最新的 WebAssembly 位元組碼技術?


為啥有人說又一次發明了 Flash 或者 Silverlight 呢……明明是又一次發明了 Java 好么(逃

好了好了認真答題。WebAssembly 主要試圖解決現有技術的一些問題:

  • JavaScript:性能不夠理想,以及語言本身的一堆坑(這個大家都懂)
  • Flash:私有技術(而且漏洞一堆),並且是純二進位格式
  • Silverlight:私有技術,並且是純二進位格式
  • 各種插件(Plug-in):安全性問題,平台兼容問題

JavaScript 的坑我想我不用講了吧,這裡隨便拉個人出來都比我講得好。重點解釋一下 WebAssembly 設計過程中考慮到的其它幾個方面:

一. 二進位格式

Web 的基礎是超文本(Hypertext),即包含超鏈接(Hyperlink)的文本(字元串)。這個特性使人能讀懂,機器也容易分析。因此,和這個理念相符的技術往往在 Web 方向上有著更大的可能性被廣泛應用——不說別的,就說後端語言吧,現在爛大街的後端語言哪個處理字元串不方便?要是都像 C 這樣 char* 滿天飛,現在後端工程師的工資估計得乘個 10。

不過呢,早期這一設計的確限制了 Web 表現力的發展。那時候標準混亂,瀏覽器們各自為政,基於有特定功能的 tag 的 HTML 的用途極為有限,尤其是當時尚未出現或完善的動態內容(XHR),多媒體內容(canvas, audio, video)以及高性能運算(WebGL,asm.js 等)等場合。

於是那時的 Flash 橫空出世。一個插件讓 Web 的表現力提升了一大截:不僅自帶矢量繪圖,動態內容,多媒體內容甚至顯卡 3D 加速也獲得了支持。在那個 JavaScript 引擎的性能還很弱的年代,Flash 讓無數開發者看到了希望。

然而隨著 HTML 相關標準的不斷完善和 JavaScript 引擎性能的突然提升,Flash 的優點沒有那麼突出了,而它的一大缺點卻暴露了出來:二進位格式。長久以來 Flash 被濫用於提供(動態和靜態)內容(我不信你們沒看過整站用 Flash 做的網站;而 Flash 在設計的時候也沒考慮過操作 DOM 的問題,畢竟人家自帶一套用戶界面),這樣一來搜索引擎和通用的文本分析方案(例如瀏覽器的搜索功能)對它束手無策。而搜索引擎幾乎已經成為 Web 內容提供的中心——它們提供到任何地方的超鏈接。於是,在 JavaScript 引擎的效率已經相當可觀的今天,Flash 不靈了。

當然了,二進位格式有其好處:相對文本格式更輕量,在互聯網上傳輸的成本更低,解釋效率也更高(如果設計得當的話)。所以 WebAssembly 最終選擇了一個妥協的方案:它要求一個程序段具有兩種可互相轉換的等價表達:二進位格式和文本格式。這二者可理解為類似機器碼和彙編的關係:傳輸和運行的時候使用二進位格式,展現給人的時候用文本格式。這樣就同時保留了二者的優點。當然了,為了安全性,要求以文本格式傳輸的程序不可被執行。

不過,由於代碼混淆和壓縮技術的廣泛應用,WebAssembly 的這一設計意圖最終不容易達到預想中的效果吧。

二. 私有技術和平台兼容性問題

Flash 的諸多漏洞(包括一堆 0day)讓人們意識到:讓一個公司的私有技術主導 Web 並不是什麼好主意。新的硬體平台?對不起,不支持。有 bug?等人家更新吧,你什麼也幹不了。高發熱?對不起,你別無選擇。沒有許可權安裝瀏覽器插件?抱歉,你就別用了。不僅僅是 Flash,Silverlight,ActiveX 插件等也是同樣的境地。

如果輪子不好用,那麼自己造一個。我們需要開源的標準。

三. 可執行代碼的安全性問題

這是黑暗森林法則的一個推論:我們不能信任任何人。網站不應該相信用戶的輸入是無害的;同樣,用戶也不應該相信網站提供的內容是無害的,尤其在這些內容會被在本地執行的時候。長期以來,我們給了傳統瀏覽器插件(Plug-in)太多的權力,而事實證明它們中的一部分正在有效地利用用戶給他們的所有權力(說你呢,支付寶)。

用戶應當有權利掌控他們的設備。

不過呢,WebAssembly 將面臨新的挑戰:一個全新的體系必將帶來更多的安全問題。

四. 性能

WebAssembly 將是一個編譯型語言。它的設計目標描述了一個美好的未來:

Define a portable, size- and load-time-efficient binary format to serve as a compilation target which can be compiled to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms, including mobile and IoT.

定義一個可移植,體積緊湊,載入迅捷的二進位格式為編譯目標,而此二進位格式文件將可以在各種平台(包括移動設備和物聯網設備)上被編譯,然後發揮通用的硬體性能以原生應用的速度運行。

五. 遠景

如果 WebAssembly 不出現,則 HTML,CSS,JavaScript 必將成為前端界的事實彙編語言:人們不斷創造更多的(他們認為更好的)對這三者的高級(high-level)描述形式,並最後以這三者作為「編譯目標」。WebAssembly 的出現則提供了一個更好的選擇:接近原生的運算效率,開源、兼容性好、平台覆蓋廣的標準,以及可以藉此機會拋棄 JavaScript 的歷史遺留問題。何樂而不為呢?


這個註定是一個長回答,像我這種不喜歡寫字的人,看到這種問題,一般都會在回答和不回答之間糾結一個星期。

下面要講的,其實是一個悲傷的故事。

Javascript ,也叫Ecma script, 是這傢伙用了 10 天時間趕出來的。。

&" dw="400" dh="300" class="content_image lazy" w="400" data-actualsrc="//i1.wp.com/pic4.zhimg.com/50/76e48a8051d234092252797a170e5670_hd.jpg">

所以,各位程序猿們,如果你覺得老闆 10 天要你們上線一個 App 是一個喪心病狂的事情,那麼可以多想想這位哥。

Youtube 上有位哥的採訪,你可以聽聽大神當年的故事。

https://www.youtube.com/watch?v=IPxQ9kEaF8c

當然,碼農和大神的區別在於:遇到這種事情,10 天以後碼農死掉了,而大神成功了。

只是但凡這種極速上線的事情,都會留下一堆的坑,大神和碼農的的區別,也就是水窪和天坑的區別。

這個是坑列表:

  • Javascript 從最開始設計,就是一種解釋型語言,因為大神覺得讓 Javascript 的目標用戶- 「非專業編程人員和設計師」,了解什麼是編譯器是一件很殘忍的事情。

  • 類型自然也是沒有的,因為學習類型就要學習 CPU 工作原理, 學習 CPU 工作原理就要學習組成原理, 大神覺得,讓 「非專業編程人和設計師」 去了解 1 和 1.0 一個是 CPU 上處理, 一個是 FPU 上面處理這種顯而易見的現象是一件很殘忍的事情。

  • 對象模型是驚人的奇葩,那是因為不想設計得和 Java 一樣強大, Netscape 當初想法是主要工作都是 Java 來完成,只有輕量級的簡單操作留給 Java script, 做為一種膠水語言( glue langurage). 現在知道為什麼叫 Java script了吧? 一個是Java, 一個是和Java 配合的 Script (腳本)。 之前還叫過Live script, 因為腳本和 Java 互動的技術叫 Live connect.

  • 對於泛型, 預設參數,操作符重載, 異常 等等這些黑科技, 大神的回答通通是:

&" dw="600" dh="450" class="origin_image zh-lightbox-thumb lazy" w="600" data-original="https://pic3.zhimg.com/f842e99d3aea905e523dc7b9d11b9547_r.jpg" data-actualsrc="//i1.wp.com/pic3.zhimg.com/50/f842e99d3aea905e523dc7b9d11b9547_hd.jpg">

好吧,異常後來加上去了。

如果故事到此為止,其實不算一個悲傷故事,大神 10 天時間完成預定目的,東西也發布了,市場反應也不錯。

但是問題是,市場反應實在是太好了,好得 Javascript 一路竄紅,紅得各大瀏覽器廠商紛紛支持, 成為瀏覽器裡面事實上的官方語言。 在這個過程中, 還順手幹掉了 VB script,

於是這個當初為 「非專業編程人員和設計師」 的解釋型語言現在居然變成互聯網上面最重要的語言之一,被用來做各種之前想也不敢想的東西,甚至還有人不顧死活的拿他來做WebOS.

於是這個時候,之前所有的小水窪都變成了天坑。之後很長段時間 JS 領域的發展史,都可以說是填坑史。

其中最大的一個坑,就是性能。

性能填坑階段一

Javascript 一開始就是解釋性語言,解釋性語言的一大特點就是慢, 而網頁應用越來越複雜,如果點個按鈕要等幾秒鐘,那淘寶的秒殺就要變成10秒殺了。這個當然不能忍。 於是聰明的人類想到一個辦法,雖然你是解釋型語言,但是我可以偷偷的編譯你啊。 這個也不需要讓這幫 「非專業編程人員和設計師」 們知道, 我只要在程序運行前的一剎那,編譯即將運行的代碼就好。你看我機不機智。

於是 Google 在 2009 年在 V8 中引入了 JIT 技術 (Just in time compiling 江湖人稱即時編譯)。 有了這個buff, Javascript 瞬間提升了 20 - 40 倍的速度。直接導致一大波大型網頁應用的出現。從此 Javascript 一騎絕塵, 飛黃騰。。呃, 好像哪裡不對嘛?

人類的性能的期望是無窮無盡的,JIT 的帶來的性能提升很快就榨乾了。實際上 JIT 有以下問題:

  • JIT 基於運行期分析編譯,而 Javascript 是一個沒有類型的語言,於是, 大部分時間,JIT 編譯器其實是在猜測 Javascript 中的類型,舉個例子:

&" dw="584" dh="45" class="origin_image zh-lightbox-thumb lazy" w="584" data-original="https://pic2.zhimg.com/0dabd0c866f65c3d3eddb73870e78342_r.jpg" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/0dabd0c866f65c3d3eddb73870e78342_hd.jpg">

JIT 看到這裡, 覺得好開心, 馬上把 add 編譯成

&" dw="584" dh="31" class="origin_image zh-lightbox-thumb lazy" w="584" data-original="https://pic2.zhimg.com/65f5decbf09c5a6c1bbc9ca47bd443ed_r.jpg" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/65f5decbf09c5a6c1bbc9ca47bd443ed_hd.jpg">

可是你隨後又幹了這樣一個事情

&" dw="580" dh="31" class="origin_image zh-lightbox-thumb lazy" w="580" data-original="https://pic1.zhimg.com/813c68eae2830fecfda7130c21e2a297_r.jpg" data-actualsrc="//i1.wp.com/pic1.zhimg.com/50/813c68eae2830fecfda7130c21e2a297_hd.jpg">

JIT 編譯器的表情肯定是

&" dw="234" dh="247" class="content_image lazy" w="234" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/3124475f966c2c6241d7612e2f407101_hd.jpg">

怎麼辦, 已經編譯成機器碼了啊。

這種情況下,JIT 編譯器只能推倒重來。JIT 帶來的性能提升,有時候還沒有這個重編的開銷大。

  • 有很多的情況下面, JIT 編譯器都無法生成代碼,比如異常, 比如 for in , 這個基本上是實現難度引起的,具體可以參考: Optimization killers · petkaantonov/bluebird Wiki · GitHub
  • 事實上,大部分時間 JIT 都不會生成優化代碼,有位元組碼的,直接位元組碼,沒有位元組碼的,粗粗編譯下就結了,因為 JIT 自己也需要時間,除非是一個函數被使用過很多遍,否則不會被編譯成機器碼,因為編譯花的時間可能比直接跑位元組碼還多。

於是,整體上 Javascript JIT 提高的性能到達的天花板還是不高的,雖然是提高了 20 - 50倍,那只是因為之前解釋執行實在是太慢了。。

性能填坑階段二。

既然 JIT 遇到的問題是類型不確定問題和有一些語言功能,比如異常,for in , JIT 起來很麻煩, 我可不可以搞個方法讓用戶不去用這些功能,同時讓他們把用的類型都標註出來啊。

按照這個思路, 催生了兩種實現路徑:

  • 一種是 Typescript, Dart, JSX 為代表的,基本思想是, 我搞個其他的語言,這個語言是強類型的,所以程序猿們需要指定類型,然後我把它編譯成 Javacript 不就行了嘛。強類型的語言編譯成弱類型還不容易,什麼,不知道怎麼編? 把類型去掉就行了嘛。
  • 另一種是火狐的 Asm.js 為代表的, 做一個 javascript 子集, 同時試圖利用標註的方法,加上變數類型, 如果覺得好難理解,這就是個典型的例子:

&" dw="584" dh="77" class="origin_image zh-lightbox-thumb lazy" w="584" data-original="https://pic2.zhimg.com/c6bf3df160b0ac8864deb12ce546dbb2_r.jpg" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/c6bf3df160b0ac8864deb12ce546dbb2_hd.jpg">

加上一堆沒有什麼卵用提示 x 其實是個 int, 然後有一個能夠識別這些符號的JS引擎,你就可以不用猜類型了哦, 事實上,由於有了類型,連傳統的 AOT 都成為了可能 (Ahead of time, 不懂的話,想像一下,就是和 C/C++ 那種編譯方式就好了)。

如果你沒有注意到,第二種的速度提升潛力比第一種要大非常多。因為第一種,無論如何,也是就是讓JIT (即時編譯) 快一點, 第二種那可直接就編譯了啊 (AOT).

這個是 Asm.js 相對於 JIT 和原生的性能對比

&" dw="909" dh="465" class="origin_image zh-lightbox-thumb lazy" w="909" data-original="https://pic4.zhimg.com/305c06e47eb648e096f92614ad2b9aa0_r.jpg" data-actualsrc="//i1.wp.com/pic4.zhimg.com/50/305c06e47eb648e096f92614ad2b9aa0_hd.jpg">

同時大家有沒後注意到,這個不是原生代碼哦, 性能堪比原生代碼, 安全性和傳統 Javascript 完全一樣。 (尼瑪,你讓插件們怎麼活)。

Web Assembly 就是第二種方式,說到底,Mozilla, Google, Microsoft, and Apple 覺得 Asm.js 這個方法有前途,想標準化一下,大家都能用。

有了大佬們的支持,Web Assembly 比 asm.js 要激進很多。 Web Assembly 連標註 Js 這種事情都懶得做了,不是要 AOT 嗎? 我直接給位元組碼好不好?(後來改成 AST 樹)。對於不支持 Web Assembly 的瀏覽器, 會有一段 Javascript 把 Web Assembly 重新翻譯為 Javascript 運行, 這個技術叫 polyfill, HTML5 剛出來的時候很常用的一個技術。

使用 AST 的原因是因為 AST 比位元組碼更容易壓縮,也更容易翻譯。

不了解 AST 可以看下面這張圖, 說明 Javascript 引擎的執行過程。 Javascript 先編譯為 AST, 然後到 Bytecode. AST 的抽象程度比 Bytecode 要高一級。

&" dw="422" dh="119" class="origin_image zh-lightbox-thumb lazy" w="422" data-original="https://pic2.zhimg.com/d51485954493e645cb307ab370c45631_r.jpg" data-actualsrc="//i1.wp.com/pic2.zhimg.com/50/d51485954493e645cb307ab370c45631_hd.jpg">

總結來說, Web Assembly 的工作方式如下:

&" dw="867" dh="238" class="origin_image zh-lightbox-thumb lazy" w="867" data-original="https://pic1.zhimg.com/a3d0d0e45057489e78b70620b739bb74_r.jpg" data-actualsrc="//i1.wp.com/pic1.zhimg.com/50/a3d0d0e45057489e78b70620b739bb74_hd.jpg">

好處是:

  • 大幅度提高 Javascript 的性能,希望能把性能這個坑填完,同時也不損失安全性。Webapp 和 原生 App 的性能差距變得很小。

  • 基本之前需要插件來提高速度這種技術已經沒有必要了, 網頁應用的移植性會變得更好。

  • 感謝@easing 提醒, WebAssembly 其實允許任何語言編譯到它制定的AST tree, 這樣子,各位就可以開開腦洞了, 因為,你可以用C/C++寫網頁了。。

PS: 這個技術我大 Opera 居然沒有參與,今天去申請了進入這個 W3C 討論組,有消息再放給大家。

=======================================================================

感謝@sapjax Gmail 不是 2009 發布的, 是2009脫離beta,原文中已經刪除其引用。

=======================================================================

更新:

哥已成功潛入 Webassembly 成員組,代表我大 Opera. 哇哈哈哈哈哈哈

On August 6, 2015, 8:09 UTC, fredrik luo became a participant in the

WebAssembly Community Group. This person was nominated by Lars Erik

Bolstad.


推薦閱讀:

瀏覽器是不是相當於 HTML、CSS 等語言的一個解釋器?
把瀏覽器的 rendering engine 翻譯成「渲染引擎」是正確的嗎?
"去你大爺的內置瀏覽器"是幹什麼的?

TAG:JavaScript | 網頁瀏覽器 | WebAssembly | asmjs | 網路位元組碼 |