當前位置: 妍妍網 > 碼農

【譯】使用.NET將WebAssembly擴充套件到雲(一)

2024-02-15碼農

點選上方「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&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 wanted"><img src="https://img.shields.io/github/issues/dotnet/runtime/help wanted? style=flat-square&amp;color=#2EA043&amp;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

    我更新了計畫檔以包含 wasi-wasm true 並註釋掉 PublishAot 相關內容。我還添加了一個runtimeconfig.template.json 檔。未對應用程式程式碼進行任何更改。

    現在,我們將整個應用程式放在一個檔包中。

    $ 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

    推薦閱讀: