【譯】使用.NET將WebAssembly擴展到雲(一)

原文 | Richard Lander

翻譯 | 鄭子銘

WebAssembly(Wasm)是一種令人興奮的新虛擬機和(彙編)指令格式。 Wasm 誕生於瀏覽器,是 Blazor 項目的重要組成部分。 Wasm 的第二個行動是針對應用程序和功能的雲計算。 WebAssembly 系統接口 (WASI) 是新的推動者,爲 WebAssembly 代碼提供了一種安全地跨語言調用和實現任意 API 的方法。現在可以使用 .NET 8 中的 wasi 實驗工作負載通過 .NET 創建 WASI 應用程序。我們正在探索這些新技術並在此環境中運行 .NET 應用程序……。真的,任何地方。

這篇文章將幫助您瞭解 Wasm 的廣泛使用,並描述 .NET 已經可以實現的功能。他們說歷史不會重演,但會押韻。我們又回來進行另一輪“一次編寫,隨處運行”。 WASI 應用程序是可移植的二進制文件,可以在任何硬件或操作系統上運行,並且不特定於任何編程語言。這一次,感覺不一樣了。這不僅僅是供應商的神經;一切都是中立的。

Wasm 和 WASI

Wasm 可能會爲我們提供雲計算的重啓,並承諾提供單一雲原生二進制文件、更高的密度和更便宜的多租戶。出於同樣的原因,它也開啓了邊緣計算的可能性。事實上,CloudFlareFastly 已經使用 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&amp;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&amp;color=%232EA043&amp;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

我更新了項目文件以包含 wasi-wasmtrue 並註釋掉 PublishAot 相關屬性。我還添加了一個runtimeconfig.template.json 文件。未對應用程序代碼進行任何更改。

現在,我們將整個應用程序放在一個文件包中。

$ 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])

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章