▲
點選上方「DotNet NB」
關註公眾號
回 復 「 1 」 獲 取 開 發 者 路 線 圖
學 習 分 享 丨 作 者 / 鄭 子 銘
這 是 D o t N e t N B 公 眾 號 的 第 226 篇 原 創 文 章
原文 | Richard Lander
轉譯 | 鄭子銘
WebAssembly(Wasm)是一種令人興奮的新虛擬機器和(組譯)指令格式。Wasm 誕生於瀏覽器,是 Blazor 計畫的重要組成部份。Wasm 的第二個行動是針對應用程式和功能的雲端運算。WebAssembly 系統介面 (WASI) 是新的推動者,為 WebAssembly 程式碼提供了一種安全地跨語言呼叫和實作任意 API 的方法。現在可以使用 .NET 8 中的 wasi 實驗工作負載透過 .NET 建立 WASI 應用程式。我們正在探索這些新技術並在此環境中執行 .NET 應用程式……。真的,任何地方。
這篇文章將幫助您了解 Wasm 的廣泛使用,並描述 .NET 已經可以實作的功能。他們說歷史不會重演,但會押韻。我們又回來進行另一輪「一次編寫,隨處執行」。WASI 應用程式是可移植的二進制檔,可以在任何硬體或作業系統上執行,並且不特定於任何程式語言。這一次,感覺不一樣了。這不僅僅是供應商的神經;一切都是中立的。
Wasm 和 WASI
Wasm 可能會為我們提供雲端運算的重新開機,並承諾提供單一雲原生二進制檔、更高的密度和更便宜的多租戶。出於同樣的原因,它也開啟了邊緣計算的可能性。事實上,CloudFlare 和 Fastly 已經使用 Wasm 在邊緣托管公共計算。
Wasm 與在 Linux 容器中執行應用程式不同,後者是對現有標準和程式碼的(良好且聰明的)重新打包。Wasm 更像是在沒有作業系統的環境中執行應用程式,只有組譯程式碼、記憶體和對外部世界的標準化(和門控)存取(透過 WASI)。
Build 2023 上的 Hyperlight 演示(4m 視訊)深入了解了支持 Wasm 的雲的外觀。它演示了在新的輕量級安全虛擬機器管理程式中執行的 Blazor 應用程式。Hyperlight 激發了新托管範例的想象力。
WebAssembly 系統介面 (WASI)、WebAssembly 介面型別 (WIT) 和 WebAssembly 元件模型是最新一輪 Wasm 創新的關鍵規範。它們基本上仍處於設計階段並正在經歷重大變化。這篇文章(以及 .NET 8 實作)以 WASI Preview 1 為中心。我們希望 .NET 9 實作使用 WASI Preview 2。
WIT 和 wit-bindgen 使用任何源語言編寫的元件都可以與主機系統進行通訊。WIT 對 C# 支持的實作由 @silesmo 領導。Wasm 和 WIT 一起定義了應用程式二進制介面(ABI)。
我們期望 WASI 成為一組標準的 WIT 型別,提供對低階功能的存取(例如獲取時間和讀取檔)。這些低階型別有效地形成了跨程式語言和作業系統的「Wasm 標準庫」。例如,我們從來沒有 Rust 開發人員和 .NET 開發人員可以同時使用的標準和共享功能。歷史上還沒有任何廣泛部署的本機程式碼公開具有 OO 形狀(如介面)的 API,可以跨程式語言和作業系統使用。
標準 WIT 型別以 wasi- 開頭,定義「平台」。您可以將它們視為與 .NET 中的系統名稱空間類似的方式(與 WASI 中的「S」匹配)。繼續類比,您可以在 System 名稱空間之外建立自己的 .NET 名稱空間,WIT 也是如此。
這些貼文在更詳細地構建 WASI 方面做得非常出色。
標準化 WASI:在 Web 之外執行 WebAssembly 的系統介面
宣布字節碼聯盟:為 WebAssembly 構建一個預設安全、可組合的未來
WebAssembly:開發人員更新的路線圖
即將到來的承諾是能夠采用現有的 .NET 應用程式或庫並將其編譯為 Wasm 目標。我們的設計本能是在 .NET 堆疊中實作相對較高的 WIT 介面(例如為 wasi-sql 建立 ADO.NET 數據提供程式),這將使現有程式碼(包括許多現有的 NuGet 包)能夠正常工作,特別是對於沒有本機依賴項。
Wasm 應用程式在 Wasm 執行時中執行,例如 wasmtime。與 Docker 非常相似,您可以使用特定功能配置該執行時。例如,如果您希望 Wasm 程式碼能夠存取鍵/值儲存,您可以向其公開一個鍵/值介面,該介面可以由本地資料庫或雲服務支持。
Wasm 執行時旨在可嵌入到應用程式中。事實上,有一個 wasmtime 包用於在 .NET 應用程式中托管 Wasm。.NET 程式碼可以作為 Wasm 執行,但 .NET 應用程式可以托管 wasmtime?!?是的,這個空間開始看起來是圓形的。雖然這些場景看起來很迴圈,但它們最終可能非常有用,與 AppDomain 的使用方式大致相似。這也讓人想起所有「docker in docker」場景。
我們期待更多的創新、更多的 Wasm 執行時和更多的行業參與者。事實上,Wasm 已經升級為 W3C 規範。W3C 是 Wasm 的完美家園,讓它成長為廣泛的行業規範,就像之前的 HTML 和 XML 一樣。
wasi-實驗工作量
.NET 8 包含一個名為 wasi-experimental 的新工作負載。它構建在 Blazor 使用的 Wasm 功能之上,將其擴充套件為在 wasmtime 中執行並呼叫 WASI 介面。它還遠未完成,但已經實作了有用的功能。
讓我們從理論轉向演示新功能。
安裝 .NET 8 SDK 後,您可以安裝 wasi-experimental 工作負載。
dotnet workload install wasi-experimental
註意:此命令可能需要管理員許可權,例如在 Linux 和 macOS 上使用 sudo。
您還需要安裝 wasmtime 來執行您即將生成的 Wasm 程式碼。
使用 wasi-console 樣版嘗試一個簡單的範例。
$ dotnet new wasiconsole -o wasiconsole
$ cd wasiconsole
$ cat Program.cs
using System;
Console.WriteLine("Hello, WASI Console!");
$ dotnet run
WasmAppHost --runtime-config /Users/rich/wasiconsole/bin/Debug/net8.0/wasi-wasm/AppBundle/wasiconsole.runtimeconfig.json
Running: wasmtime run --dir . -- dotnet.wasm wasiconsole
Using working directory: /Users/rich/wasiconsole/bin/Debug/net8.0/wasi-wasm/AppBundle
Hello, WASI Console!
該應用程式使用 wasmtime 執行。這裏沒有 x64 或 Arm64,只有 Wasm。
dotnet run 提供額外的資訊(在控制台輸出中)來幫助解釋發生了什麽。未來這種情況可能會改變。與主機系統的所有互動均由 wasmtime 管理。
我們可以更深入地檢視 AppBundle 目錄。
$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle
total 24872
-rwxr--r-- 1 rich staff 11191074 Oct 31 07:53 dotnet.wasm
-rwxr--r-- 1 rich staff 1526128 Oct 11 14:00 icudt.dat
drwxr-xr-x 6 rich staff 192 Nov 1919:35 managed
-rwxr-xr-x 1 rich staff 48 Nov 1919:35 run-wasmtime.sh
-rw-r--r-- 1 rich staff 915 Nov 19 19:35 runtimeconfig.bin
drwxr-xr-x 2 rich staff 64 Nov 1919:35 tmp
-rw-r--r-- 1 rich staff 1457 Nov 19 19:35 wasiconsole.runtimeconfig.json
$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle/managed
total 3432
-rw-r--r-- 1 rich staff 27136 Nov 19 19:35 System.Console.dll
-rw-r--r-- 1 rich staff 1711616 Nov 19 19:35 System.Private.CoreLib.dll
-rw-r--r-- 1 rich staff 5632 Nov 19 19:35 System.Runtime.dll
-rw-r--r-- 1 rich staff 5120 Nov 19 19:35 wasiconsole.dll
SDK 將應用程式釋出到獨立部署中。.NET 執行時 — dotnet.wasm — 已經編譯為 Wasm(在我們的構建機器上)。應用程式和 dotnet.wasm 在 wasmtime 中一起載入,執行所有程式碼。應用程式的實際受控代碼(位於托管目錄中)在執行時解釋,就像 Blazor WebAssembly 一樣。 @yowl 和 @SingleAccretion 社群成員一直在嘗試 Wasm 和原生 AOT。
您可能想知道為什麽我們需要將所有這些檔分開,而顯然更好的選擇是擁有一個 wasiconsole.wasm 檔。我們也可以這樣做,但稍後會在貼文中介紹它,因為我們需要在機器上安裝更多的軟體(目前 wasi 實驗工作負載不包含這些軟體)。
RuntimeInformation 告訴我們什麽?
RuntimeInformation 是我最喜歡的型別之一。它讓我們更好地了解目標環境。
我們可以稍微更改範例以顯示一些更有用的資訊。
using System;
using System.Runtime.InteropServices;
Console.WriteLine($"Hello {RuntimeInformation.OSDescription}:{RuntimeInformation.OSArchitecture}");
Console.WriteLine($"With love from {RuntimeInformation.FrameworkDescription}");
它產生這個輸出。
Hello WASI:Wasm
With love from .NET 8.0.0
第一行很有趣。作業系統是WASI,架構是Wasm。這是有道理的,有更多的背景。文章前面提到 Wasm 可以被認為是「無作業系統」,但是我們不能簡單地稱之為 Wasm,因為現有的瀏覽器和 WASI 環境有很大不同。因此,該環境唯一一致的名稱是 WASI,而 Wasm 明確是「芯片架構」。
Wasm 是一個 32 位計算環境,這意味著 2^32 字節是可尋址的。但是,Wasm 執行時可以配置為使用 memory64,從而可以存取 >4GB 的記憶體。我們還沒有對此的支持。
存取主機檔案系統
Wasmtime(和其他 Wasm 執行時)提供將主機目錄對映到來賓目錄的選項。從使用者的角度來看,這與使用 Docker 進行卷掛載類似,但實作細節有所不同。
讓我們看一個依賴目錄安裝的簡單應用程式。它使用 Markdig 包將 markdown 轉換為 HTML。公平地說,Markdig 並不是為了以 Wasm 的身份執行而編寫的。只要能夠為其建立一個舒適的管理環境,Markdig 就會很高興,這就是我們所做的。
讓我們在 Mac M1 (Arm64) 機器上嘗試一下。
$ pwd
/Users/rich/git/wasm-samples/tomarkup
$ dotnet publish
$ cd bin/Release/net8.0/wasi-wasm/AppBundle
$ cat run-wasmtime.sh
wasmtime run --dir . dotnet.wasm tomarkup $*
$ ./run-wasmtime.sh
A valid inputfile must be provided.
$ wasmtime run --dir . --mapdir /markdown::/Users/rich/markdown --mapdir /tmp::/Users/rich dotnet.wasm tomarkup $* /markdown/README.md /tmp/README.html
$ ls ~/*.html
/Users/rich/README.html
$ cat ~/markdown/README.md | head -n 3
# .NET Runtime
[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&branchName=main)
$ cat ~/README.html | head -n 3
<h1>.NET Runtime</h1>
<p><a href="https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=129&branchName=main"><img src="https://dev.azure.com/dnceng-public/public/_apis/build/status/dotnet/runtime/runtime?branchName=main" alt="Build Status" /></a>
<a href="https://github.com/dotnet/runtime/labels/help wanted"><img src="https://img.shields.io/github/issues/dotnet/runtime/help wanted? style=flat-square&color=#2EA043&label=help wanted" alt="Help Wanted" /></a>
--mapdir 正在掛載從主機到來賓的目錄。
如您所見,Markdown 檔已轉換為 HTML。為了簡潔起見,顯示了每個檔的前三行。
目錄掛載所需的 CLI 手勢目前有點不方便。這是我們需要在未來版本中考慮的內容。這實際上是一個 dotnet run 和 wasmtime run 應該如何關聯的問題。
但它能算字數嗎?
我最近出版了【System.IO 的便利】,重點關註字數統計。我們能否獲得與 Wasm 相同的程式碼來執行並看看它的執行速度有多快?
該文章中的字數統計基準測試在 Linux x64 上執行。讓我們保持不變,但這次以 Wasm 身份執行。
$ pwd
/Users/rich/git/convenience/wordcount/count
$ grep asm count.csproj
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
<WasmSingleFileBundle>true</WasmSingleFileBundle>
$ dotnet publish
$ cd bin/Release/net8.0/wasi-wasm/AppBundle/
$ WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total
我更新了計畫檔以包含
現在,我們將整個應用程式放在一個檔包中。
$ ls -l bin/Release/net8.0/wasi-wasm/AppBundle/
total 6684
-rw-r--r-- 1 rich rich 1397 Nov 1919:59count.runtimeconfig.json
-rwxr-xr-x 1 rich rich 6827282 Nov 1919:59count.wasm
-rw-r--r-- 1 rich rich 915 Nov 1919:59 runtimeconfig.bin
-rwxr-xr-x 1 rich rich 27 Nov 1919:59 run-wasmtime.sh
drwxr-xr-x 2 rich rich 4096 Nov 1919:59 tmp
看起來好多了。該應用程式只有不到 7MB。我必須安裝 WASI-SDK 才能使用 WasmSingleFileBundle 內容並設定環境變量以使 dotnetpublish 能夠找到所需的工具。
$ echo$WASI_SDK_PATH
/home/rich/wasi-sdk/wasi-sdk-20.0/
wasmtime 最近發生了重大變化。我選擇使用 WASMTIME_NEW_CLI=0 來恢復執行範例的舊行為。
讓我們回到效能。首先,作為 wasm 執行(透過直譯器執行受控代碼):
$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 821
Elapsed time (us): 821223.8
real 0m0.897s
user 0m0.846s
sys 0m0.030s
現在有了我們對 Wasm 的(甚至更多)實驗性原生 AOT 支持。
$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir /text::/home/rich/git/convenience/wordcount count.wasm $* /text/Clarissa_Harlowe
11716 110023 610515 /text/Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 /text/Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 /text/Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 /text/Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 /text/Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 /text/Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 /text/Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 /text/Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 /text/Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 /text/Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 60
Elapsed time (us): 60322.2
real 0m0.107s
user 0m0.064s
sys 0m0.045s
現在,在 Linux x64 上使用 CoreCLR 執行:
$ time ./app/count ../Clarissa_Harlowe/
11716 110023 610515 ../Clarissa_Harlowe/clarissa_volume1.txt
12124 110407 610557 ../Clarissa_Harlowe/clarissa_volume2.txt
11961 109622 606948 ../Clarissa_Harlowe/clarissa_volume3.txt
12168 111908 625888 ../Clarissa_Harlowe/clarissa_volume4.txt
12626 108593 614062 ../Clarissa_Harlowe/clarissa_volume5.txt
12434 107576 607619 ../Clarissa_Harlowe/clarissa_volume6.txt
12818 112713 628322 ../Clarissa_Harlowe/clarissa_volume7.txt
12331 109785 611792 ../Clarissa_Harlowe/clarissa_volume8.txt
11771 104934 598265 ../Clarissa_Harlowe/clarissa_volume9.txt
9 153 1044 ../Clarissa_Harlowe/summary.md
109958 985714 5515012 total
Elapsed time (ms): 77
Elapsed time (us): 77252.9
real 0m0.128s
user 0m0.096s
sys 0m0.014s
這些都是有趣的結果。我們有解釋、AOT 和 JIT 程式碼生成方法可供比較。Wasm 直譯器能夠在不到一秒的時間內計算(略低於)一百萬個單詞,而 AOT 編譯的 Wasm 和 JIT 執行時可以在大約 100 毫秒內完成同樣的操作。
註意:Main 方法是執行 main 的時間,由 StopWatch 測量。流程是整個流程的持續時間,以時間來衡量。
此圖表顯示了上下文中的所有結果,包括 System.IO 的便利性貼文中的結果。
wasmtime JIT 將 Wasm 程式碼編譯到目標環境(在本例中為 Linux+x64)。例如,可以使用 wamr 對 Wasm 程式碼進行 AOT。我將把它留到另一篇文章中。
原文連結
Extending WebAssembly to the Cloud with .NET
推薦閱讀: