当前位置: 欣欣网 > 码农

【译】使用.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

    推荐阅读: