原文 | 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 方面做得非常出色。
即將到來的承諾是能夠採用現有的 .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 19 19:35 managed
-rwxr-xr-x 1 rich staff 48 Nov 19 19: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 19 19: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%20wanted"><img src="https://img.shields.io/github/issues/dotnet/runtime/help%20wanted?style=flat-square&color=%232EA043&label=help%20wanted" 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 19 19:59 count.runtimeconfig.json
-rwxr-xr-x 1 rich rich 6827282 Nov 19 19:59 count.wasm
-rw-r--r-- 1 rich rich 915 Nov 19 19:59 runtimeconfig.bin
-rwxr-xr-x 1 rich rich 27 Nov 19 19:59 run-wasmtime.sh
drwxr-xr-x 2 rich rich 4096 Nov 19 19: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
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。
歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含鏈接: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。
如有任何疑問,請與我聯繫 ([email protected])