1. 程式人生 > >JavaScript是如何工作的:與WebAssembly比較及其使用場景

JavaScript是如何工作的:與WebAssembly比較及其使用場景

摘要: WebAssembly未來可期。

Fundebug經授權轉載,版權歸原作者所有。

這是專門探索 JavaScript及其所構建的元件的系列文章的第6篇。

如果你錯過了前面的章節,可以在這裡找到它們:

這次將講解WebAssembly是如何工作的,更重要的是,它是如何在效能方面與JavaScript進行比較的:載入時間、執行速度、垃圾收集、記憶體使用、API開放平臺、除錯、多執行緒和可移植性。

首先,讓我們看看WebAssembly做什麼

首先,我們有必要了解一下asm.js。2012年,Mozilla 的工程師 Alon Zakai 在研究 LLVM 編譯器時突發奇想:許多 3D 遊戲都是用 C / C++ 語言寫的,如果能將 C / C++ 語言編譯成 JavaScript 程式碼,它們不就能在瀏覽器裡運行了嗎?眾所周知,JavaScript 的基本語法與 C 語言高度相似。於是,他開始研究怎麼才能實現這個目標,為此專門做了一個編譯器專案

Emscripten。這個編譯器可以將 C / C++ 程式碼編譯成 JS 程式碼,但不是普通的 JS,而是一種叫做 asm.js 的 JavaScript 變體,效能差不多是原生程式碼的50%

之後Google開發了Portable Native Client,也是一種能讓瀏覽器執行C/C++程式碼的技術。 後來可能是因為彼此之間有共同的更高追求,Google, Microsoft, Mozilla, Apple等幾家大公司一起合作開發了一個面向Web的通用二進位制和文字格式的專案,那就是WebAssembly。asm.js 與 WebAssembly 功能基本一致,就是轉出來的程式碼不一樣:asm.js是文字,WebAssembly是二進位制位元組碼,因此執行速度更快、體積更小

WebAssembly(又稱 wasm) 是一種新的位元組碼格式,主流瀏覽器都已經支援 WebAssembly。 和 JS 需要解釋執行不同的是,WebAssembly 位元組碼和底層機器碼很相似可快速裝載執行,因此效能相對於 JS 解釋執行大大提升。 也就是說 WebAssembly 並不是一門程式語言,而是一份位元組碼標準,需要用高階程式語言編譯出位元組碼放到 WebAssembly 虛擬機器中才能執行, 瀏覽器廠商需要做的就是根據 WebAssembly 規範實現虛擬機器。

WebAssembly 載入時間

WebAssembly在瀏覽器中載入速度更快,因為只有已經編譯好的wasm檔案需要通過internet傳輸。wasm是一種低階組合語言,具有非常簡潔的二進位制格式。

WebAssembly 執行速度

如今 Wasm執行速度只比原生程式碼慢20%,這是一個令人驚喜的結果。它是這樣的一種格式,會被編譯進沙箱環境中且在大量的約束條件下執行以保證沒有任何安全漏洞或者使之強化。和真正的原生程式碼比較,執行速度的下降微乎其微。更重要的是,未來將會更加快速。

更好的是,它與瀏覽器無關——所有主要引擎都增加了對WebAssembly的支援,且執行速度相差無幾。

為了理解與JavaScript相比WebAssembly的執行速度有多快,應該首先閱讀關於JavaScript引擎如何工作的文章。

讓我們快速瀏覽下 V8 的執行機制:

在左邊,是一些JavaScript原始碼,包含JavaScript函式。首先需要解析它,以便將所有字串轉換為標記並生成抽象語法樹(AST)。AST 是JavaScript程式邏輯結構在記憶體中的表示形式。一旦生成了 AST,V8 直接進入到機器碼階段。其後遍歷樹,生成機器碼,就得到了編譯好的函式,在這個過程中是沒有提高遍歷速度的。

現在,讓我們看看V8管道在下一階段的工作:

現在有了V8 的新的優化編譯器 (TurboFan), 當 JavaScript應用程式在執行時,很多程式碼都在 V8 中執行。TurboFan 監測是否有程式碼執行緩慢,是否存在效能瓶頸和熱點(記憶體使用過高的地方),以便對其進行優化。它把以上監視得到的程式碼推向後端即優化過的即時編譯器,該編譯器把消耗大量 CPU 資源的函式轉換為效能更優的程式碼。

它解決了效能的問題,但這種處理方式有個缺點,分析程式碼和決定優化哪些內容的過程也會消耗CPU,這意味著更高的耗電量,特別是在移動裝置上

但是,wasm 並不需要以上的全部步驟-如下所示是它被插入到執行過程示意圖:

在編譯階段,WebAssembly 不需要被轉換,因為它已經是位元組碼了。總之,以上的解析不再需要,你擁有優化後的二進位制程式碼可以直接插入到後端(即時編譯器)並生成機器碼。編譯器在前端已經完成了所有的程式碼優化工作。

由於跳過了編譯過程中的不少步驟,這使得 wasm 的執行更加高效。

WebAssembly 記憶體模型

例如,編譯 成WebAssembly 的c++ 程式的記憶體是一個連續的記憶體塊,其中沒有“漏洞”。wasm 有助於提高安全性的一個特性是執行堆疊與線性記憶體分離的概念。在 c++ 程式中,如果有一個堆,從堆的底部進行分配,然後從其頂部獲得記憶體來增加記憶體堆疊的大小。你可以獲得一個指標然後在堆疊記憶體中遍歷以操作你不應該接觸到的變數。

這是大多數可疑軟體可以利用的漏洞。

WebAssembly採用了完全不同的內在模式。執行堆疊與 WebAssembly 程式本身是分開的,因此無法在其中修改和更改諸如變數的值。同樣,這些函式使用整數偏移量,而不是指標。函式指向一個間接函式表。之後,這些直接的計算出的數字進入模組中的函式。通過這種方式構建的,可以同時載入多個 wasm 模組,偏移所有索引且每個模組都執行良好。

更多關於 JavaScript 記憶體模型和管理的文章詳見這裡

WebAssembly 垃圾收集

在 JavaScript 中,開發者不需要擔心記憶體中無用變數的回收。JS 引擎使用一個叫垃圾回收器的東西來自動進行垃圾回收處理。

現在,WebAssembly 根本不支援垃圾回收。記憶體是手動管理的(就像 C/C++)。雖然這些可能讓開發者程式設計更困難,但它的確提升了效能。

目前,WebAssembly 是專門圍繞 C++ 和 RUST 的使用場景設計的。由於 wasm 是非常底層的語言,這意味著只比組合語言高一級的程式語言會容易被編譯成 WebAssembly。C 語言可以使用 malloc,C++ 可以使用智慧指標,Rust 使用完全不同的模式(一個完全不同的話題)。這些語言沒有使用記憶體垃圾回收器,所以他們不需要所有複雜執行時的東西來追蹤記憶體。WebAssembly 自然就很適合於這些語言。

另外,這些語言並不能夠 100% 地應用於複雜的 JavaScript 使用場景比如監聽 DOM 變化 。用 C++ 來寫整個的 HTML 程式是毫無意義的因為 C++ 並不是為此而設計的。大多數情況下,工程師用使用 C++ 或 Rust 來編寫 WebGL 或者高度優化的庫(比如大量的數學運算)。

然而,將來 WebAssembly 將會支援不帶記憶體垃圾回功能的的語言。

WebAssembly 平臺介面訪問

依賴於執行 JavaScript 的執行時環境,對特定於平臺的api的訪問是公開的,可以通過 JavaScript 程式來直接訪問這些平臺所暴露出的指定介面。例如,如果您在瀏覽器中執行JavaScript,有一組Web API, Web 應用程式可以呼叫這些API來控制Web瀏覽器/裝置功能,並訪問 DOM、CSSOM、WebGL、IndexedDB、Web Audio API 等等。

然而,WebAssembly 模組不能訪問任何平臺api。所有的這一切都得由 JavaScript 來進行中轉。如果想在 WebAssembly 模組中訪問一些特定於平臺的api,必須通過JavaScript呼叫它。

例如,如果想使用console.log,你必須通過JavaScript呼叫它,而不是C++程式碼。而這些 JavaScript 呼叫會產生一定的效能損失。

情況不會一成不變的。規範將會為在未來為 wasm 提供訪問指定平臺的介面,這樣你就可以不用在你的程式中內建 JavaScript。

從原始碼轉換講起

JavaScript指令碼正變得越來越複雜。大部分原始碼(尤其是各種函式庫和框架)都要經過轉換,才能投入生產環境。

常見的原始碼轉換,主要是以下三種情況:

  1. 壓縮,減小體積。比如jQuery 1.9的原始碼,壓縮前是252KB,壓縮後是32KB。
  2. 多個檔案合併,減少HTTP請求數。
  3. 其他語言編譯成JavaScript。最常見的例子就是CoffeeScript。

這三種情況,都使得實際執行的程式碼不同於開發程式碼,除錯(debug)變得困難重重。如果你想提高Debug效率,歡迎免費試用Fundebug

通常,JavaScript的直譯器會告訴你,第幾行第幾列程式碼出錯。但是,這對於轉換後的程式碼毫無用處。舉例來說,jQuery 1.9壓縮後只有3行,每行3萬個字元,所有內部變數都改了名字。你看著報錯資訊,感到毫無頭緒,根本不知道它所對應的原始位置。

這就是Source map想要解決的問題。

Source map

簡單說,Source map就是一個資訊檔案,裡面儲存著位置資訊。也就是說,轉換後的程式碼的每一個位置,所對應的轉換前的位置。有了它,出錯的時候,除錯工具將直接顯示原始程式碼,而不是轉換後的程式碼。這無疑給開發者帶來了很大方便。

由於沒有規範定義Source map,所以目前WebAssembly並不支援,但最終會有的(可能快了)。當你在 C++ 程式碼中設定了斷點,你將會看到 C++ 程式碼而不是 WebAssembly。至少,這是 WebAssembly 原始碼對映的目標。

閱讀Source Map入門教程,闊以瞭解更多細節。

多執行緒

JavaScript 是單執行緒的。有一些方法可以利用事件迴圈並利用非同步程式設計,這個之前在 JavaScript是如何工作的:事件迴圈和非同步程式設計的崛起+ 5種使用 async/await 更好地編碼方式 已經講過了。

JavaScript 也使用 Web Workers 但是隻有在極其特殊的情況下-大體上,可以把任何可能阻塞 UI 主執行緒的密集的 CPU 計算移交給 Web Worker 執行以獲得更好的效能。但是,Web Worker 不能夠訪問 DOM。

目前WebAssembly不支援多執行緒。但是,這有可能是接下來 WebAssembly 要實現的。Wasm 將會接近實現原生的執行緒(比如,C++ 風格的執行緒)。擁有真正的執行緒將會在瀏覽器中創造出很多新的機遇。並且當然,會增加濫用的可能性。

可移植性

現在JavaScript幾乎可以在任何地方執行,從瀏覽器到伺服器端,甚至在嵌入式系統中。

WebAssembly的設計宗旨是安全、便攜。就像JavaScript。它將執行在每個支援 wasm 的環境中(例如,每個瀏覽器)。

WebAssembly 擁有和早年 Java 使用 Applets 來實現可移植性的同樣的目標。

WebAssembly 使用場景

WebAssembly 的最初版本主要是為了解決大量計算密集型的計算的(比如處理數學問題)。最為主流的應用場景就是遊戲——處理大量的畫素。你可以使用你熟悉的 OpenGL 繫結來編寫 C++/Rust 程式,然後編譯成 wasm。之後,它就可以在瀏覽器中執行。

在瀏覽器中

  • 更好的讓一些語言和工具可以編譯到 Web 平臺執行。
  • 圖片/視訊編輯。
  • 遊戲:
    • 需要快速開啟的小遊戲
    • AAA 級,資源量很大的遊戲。
    • 遊戲門戶(代理/原創遊戲平臺)
  • P2P 應用(遊戲,實時合作編輯)
  • 音樂播放器(流媒體,快取)
  • 影象識別
  • 視訊直播
  • VR 和虛擬現實
  • CAD 軟體
  • 科學視覺化和模擬
  • 互動教育軟體和新聞文章。
  • 模擬/模擬平臺(ARC, DOSBox, QEMU, MAME, …)。
  • 語言編譯器/虛擬機器。
  • POSIX使用者空間環境,允許移植現有的POSIX應用程式。
  • 開發者工具(編輯器,編譯器,偵錯程式…)
  • 遠端桌面。
  • VPN。
  • 加密工具。
  • 本地 Web 伺服器。
  • 使用 NPAPI 分發的外掛,但受限於 Web 安全協議,可以使用 Web APIs。
  • 企業軟體功能性客戶端(比如:資料庫)

脫離瀏覽器

  • 遊戲分發服務(便攜、安全)。
  • 服務端執行不可信任的程式碼。
  • 服務端應用。
  • 移動混合原生應用。
  • 多節點對稱計算

原文:https://blog.sessionstack.com…

編輯中可能存在的bug沒法實時知道,事後為了解決這些bug,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug。

你的點贊是我持續分享好東西的動力,歡迎點贊!

一個笨笨的碼農,我的世界只能終身學習!

更多內容請關注公眾號《大遷世界》!

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!

版權宣告

轉載時請註明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/12/24/how-does-webassembly-works/