C# 在 WASM 体积上已超越 Rust
摘要
NetWasm 是一个独立的编译器、CoreLib、运行时和 SDK,可将 C# 和 .NET CIL 直接编译为 WebAssembly,生成的二进制文件比体积优化后的 Rust 更小(47,895 字节对 53,635 字节),同时在 GC 基准测试中优于 Mono AOT。
查看缓存全文
缓存时间: 2026/10/21 09:18
zion-sati/NetWasm 来源:https://github.com/zion-sati/NetWasm
NetWasm
面向 WebAssembly 和 WASI 的独立 .NET。
用 C# 编写,部署小巧的独立 Wasm。NetWasm 是一个独立的编译器、CoreLib、运行时和 SDK,可将 C# 和 .NET CIL 直接编译为 WebAssembly。它不会将桌面 CLR、Mono、dotnet.js 或应用程序 DLL 带入生产环境。
在 GitHub 上为 NetWasm 点 Star(https://github.com/zion-sati/NetWasm)
发布(https://github.com/zion-sati/NetWasm/actions/workflows/release.yml)
将 C# 编译为 WebAssembly(https://www.netwasm.com/compile-csharp-to-webassembly/) · 在浏览器中试用(https://playground.netwasm.com/) · 安装并构建 · 查看支持的 API · 阅读文档
C# 在 Wasm 体积上已超越 Rust
NetWasm + 精确 GC:47,895 字节
Rust 1.95.0 体积优化配置:53,635 字节
在相同的打印 42 工作负载下,NetWasm 的结果包含了运行时和精确的 Compact 垃圾收集器。两个产物均为采用体积优化设置构建的独立 WASI Preview 2 组件。
原始体积小 10.7%。Brotli-11 压缩后小 19.5%。
Wasm 体积对比图,包含原始和 Brotli-11 尺寸。NetWasm(精确 GC)原始 47,895 字节,压缩后 13,660 字节;Rust 为 53,635 和 16,963;Kotlin/Wasm 为 79,293 和 22,655;TinyGo 为 93,043 和 26,931。NetWasm 与 Rust 为同一 WASI Preview 2 的对比;Kotlin 和 TinyGo 为 Preview 1 的参考上下文。
| 工具链 | 原始 Wasm | Brotli 11 | 对比角色 |
|---|---|---|---|
| NetWasm,Compact GC | 47,895 B | 13,660 B | WASI P2 组件;含精确 GC |
| Rust 1.95.0,体积配置 | 53,635 B | 16,963 B | 同条件 WASI P2 组件参考 |
Kotlin 2.4.0 wasmWasi | 79,293 B | 22,655 B | P1 上下文;GC 由宿主引擎提供 |
| TinyGo 0.39.0 | 93,043 B | 26,931 B | P1 上下文;已配置精确 GC |
Rust 是主要对比对象,因为它使用相同的工作负载和输出形式。Kotlin/Wasm 和 TinyGo 展示了更广泛的体积格局,但它们的编译目标和运行时边界有所不同。这是针对特定工作负载/配置的实测结果,而非全语言范围的体积排名。
比 Mono AOT 更快
NetWasm 的分配和垃圾收集基准测试运行速度比 Mono AOT 更快,两者由相同的 C# 源码编译,并由相同的 Wasmtime 运行:
| 工作负载 | NetWasm,NetWasmGC | Mono AOT,.NET 10 | NetWasm 耗时 |
|---|---|---|---|
| 完整事务 | 43.9 ms | 59.1 ms | 少 26% |
| 持有对象图 | 8.8 ms | 14.2 ms | 少 38% |
| UI churn | 17.6 ms | 29.1 ms | 少 40% |
以上数据为 Apple M1 Max 上 Wasmtime 49.0.3 下 15 次运行的中位数,每次运行均与桌面 .NET 的结果进行了核对。Mono AOT 指 Microsoft 的 wasi-wasm 目标,默认 Release 配置。NetWasmGC 在前两个工作负载上也比桌面 .NET 11 的 JIT 耗时更少(49.9 和 9.7 ms);JIT 在 UI churn 上更快(8.6 ms),而桌面 NativeAOT 在三项上均为最快。
不止 Hello World
46.7 KiB 的程序只是基线,而非边界。
C# 15 · JavaScript 模块和 Web Workers · 静态 .a 互操作 · WIT/WASI P2 · 表达式树 · HTTP/JSON/XML/Regex · 无反射 DI · TUnit/VSTest · wasm64 · 四种分配策略
目前 NetWasm 可以:
- 通过常规
dotnetCLI 构建、运行、发布和测试项目; - 将 C# 导出发布为带类型的 JavaScript 模块,可在页面中或 Web Worker 中运行;
- 链接兼容的 Wasm
.a归档,并生成受限的 C ABI 编组; - 从 WIT 生成 C# 绑定 并产出独立的 WASI P2 组件;
- 无需通用运行时反射即可构造和检查表达式树;
- 使用独立打包的 LINQ、HTTP、JSON、XML、Regex、哈希和依赖注入库;
- 为
netwasm0.1和桌面 .NET 双目标打包库。
该配置刻意比桌面 .NET 更小。它已经支持完整应用、可移植库、测试项目和浏览器模块。
试一试
安装 .NET SDK 10.0.303 或更高版本(https://dotnet.microsoft.com/download/dotnet/10.0),然后运行:
dotnet new install NetWasm.Templates
dotnet new netwasm-app -n HelloNetWasm
cd HelloNetWasm
dotnet restore
dotnet run
dotnet publish -c Release -o publish/local
使用兼容的 WASI Preview 2 宿主(如 Wasmtime(https://wasmtime.dev/))运行已发布的组件:
wasmtime run publish/local/HelloNetWasm.wasm
42
已发布的组件在 .NET 方面是自包含的,但仍需要一个 WebAssembly 宿主。
用 .NET 构建;无需 .NET 运行。
使用 C# 15 的项目将选择 .NET 11 SDK;较早的语言版本可以继续使用 .NET 10。SDK 通过 NuGet 还原其固定版本的 Node、LLD 和 Binaryen 工具,因此普通应用无需额外安装 Emscripten、Git 或 Python。
详见完整快速入门,了解宿主、还原、Windows、浏览器和双目标相关细节。
它如何保持小巧
Roslyn 产出 CIL。NetWasm 将可达程序直接编译为 Wasm,特化其泛型,并只链接它所需的运行时支持。链接后的 Wasm 组件就是程序本身。
- 封闭世界编译。 不可达的代码、导入和运行时特性不会进入产物。
- 更小的平台契约。 NetWasm 支持可移植 API,而非复刻每一个桌面运行时假设。
- 没有通用运行时反射。 运行时类型名称、动态程序集加载和
dynamic关键字不在该配置范围内。 - 生成式边界。 JSON、DI、WIT 和原生互操作使用已知契约,而非运行时发现。
- 可选数据和诊断。 时区数据、丰富堆栈跟踪及其他部署数据仅在被选中时才包含。
- 单一收集器。 只链接所选的分配策略。
这是一个全新的 .NET 平台,而非被压缩进 Wasm 沙箱的桌面 .NET。正是这一边界使得原生 Wasm 体积级别的 C# 成为可能。
选择你的分配策略
Compact 是默认项:一个体积优先、精确、非移动的标记-清除收集器。它追踪编译器发布的根和引用布局,无需保守扫描。其名称指的是实现体积,而非移动堆压缩。
| 策略 | 适用场景 | 原始组件 | Brotli 11 |
|---|---|---|---|
| Compact(默认) | 小型应用和低 churn UI | 47,895 B | 13,660 B |
| NetWasmGC | 更高分配吞吐量 | 50,855 B | 15,053 B |
| Bump | 有界的一次性任务 | 43,864 B | 11,959 B |
在可执行项目的 .csproj 中选择策略:
<NetWasmGC>Bump</NetWasmGC>
不设置该属性即选择 Compact。库保持收集器无关;所有四种后端均已为 wasm32 和 wasm64 打包。
详见选择垃圾收集器了解权衡与诊断。
库应向上游走
为更小的 NetWasm 配置编写的库,也应当在桌面 .NET 上有用,而不是被困在仅限 Wasm 的生态中。
dotnet new netwasm-lib -n MyLibrary
该模板同时以 netwasm0.1 和 net10.0 为目标,为两个平台生成 NuGet 资产。这是显式的多目标,而非承诺任意桌面包可以反向使用。
NetWasm.Libraries(https://github.com/zion-sati/NetWasm.Libraries)提供已移植的框架和扩展包。
TUnit-NetWasm(https://github.com/zion-sati/TUnit-NetWasm)通过通用 VSTest 桥接提供常规的 dotnet test 集成。
与传统 .NET Wasm 相比如何?
针对一个打印 42 的空 UI Blazor WebAssembly 应用,启用 AOT、托管裁剪、IL 剥离、不变全球化和体积优先的原生链接后:
AOT Wasm 体积对比,越低越好:NetWasm(Compact GC)46.8 KiB,Blazor .NET 10 为 11.71 MiB,Blazor .NET 11 RC1 为 14.58 MiB。按精确字节计算,NetWasm 比 .NET 11 构建小约 319 倍。
| AOT 构建 | 未压缩 Wasm | 相对 NetWasm |
|---|---|---|
| NetWasm,Compact GC | 47,895 B | 1× |
| Blazor WebAssembly,.NET 10 | 12,276,684 B | 256× |
| Blazor WebAssembly,.NET 11 RC1 | 15,289,241 B | 319× |
Blazor 总量包含其原生运行时和必需的 Webcil 程序集;NetWasm 包含其运行时、垃圾收集器和 WASI 组件。这是一个控制台组件与空 UI 应用的对比,并非等价框架的比较,也不是对所有 .NET 工作负载的结论。
当前边界
NetWasm 处于 1.0 之前阶段。其支持的配置今天即可使用,并有已发布的资格验证覆盖,但源码、包、目标配置和 ABI 契约可能在次要版本之间发生变化。常规的可移植输出是 wasm32 WASI Preview 2 组件。原始 wasm32、原始 wasm64 和 JavaScript 宿主集成是高级替代方案;wasm64 组件仍受上游工具链限制。
运行时反射、dynamic、运行时程序集加载和托管线程不在配置范围内。通用的 System.IO.File/Directory/FileStream、原始套接字和子进程 API 尚未实现。
现有的 NuGet 包仅在其可达代码和 API 依赖符合支持配置时才能工作。
通往 1.0 之路
NetWasm 1.0 意味着一个有用、文档完善且稳定的小型平台。它不意味着“桌面 .NET,只不过在 Wasm 里“。主要待完成主题:
- 更广泛的 WASI Preview 2 覆盖;
- 全球化模式和可选文化数据;
- 表达式树的完善;
- 更快的增量开发循环;
- 稳定的
netwasm1.0平台和运行时契约; - 生产诊断和更广泛的宿主资格验证。
NetWasm 0.10.0 新特性: 重建的垃圾收集器。对象不再携带收集器头,数组为单次分配,NetWasmGC 通过推进指针进行分配,Compact 拥有为体积和内存而设计的全新核心。分配密集型程序运行更快且内存占用大幅降低,附带时区数据的应用启动时间缩短为原来的几分之一。Boehm 收集器已被移除。
进行中: 与现有 .NET Standard 2.1 库二进制和 NuGet 资产的更广泛兼容。目标是在可达代码符合 NetWasm 配置时无需修改即可使用这些库,同时不将未使用的不支持路径带入应用。这不会把 NetWasm 变成桌面 .NET。可达的通用运行时反射、dynamic、运行时程序集加载、托管线程、不受支持的宿主 API 和命名文化全球化仍在初始兼容切片之外,会在编译时失败。
软件包
编译器、SDK、运行时和托管包
NetWasm.Compiler.Browser(https://www.nuget.org/packages/NetWasm.Compiler.Browser) · NetWasm.Compiler.Tasks(https://www.nuget.org/packages/NetWasm.Compiler.Tasks) · NetWasm.Hosting(https://www.nuget.org/packages/NetWasm.Hosting) · NetWasm.Hosting.Build(https://www.nuget.org/packages/NetWasm.Hosting.Build) · NetWasm.Ref(https://www.nuget.org/packages/NetWasm.Ref) · NetWasm.Runtime.Pack(https://www.nuget.org/packages/NetWasm.Runtime.Pack) · NetWasm.Runtime.Wasm32(https://www.nuget.org/packages/NetWasm.Runtime.Wasm32) · NetWasm.Runtime.Wasm64(https://www.nuget.org/packages/NetWasm.Runtime.Wasm64) · NetWasm.Sdk(https://www.nuget.org/packages/NetWasm.Sdk) · NetWasm.Templates(https://www.nuget.org/packages/NetWasm.Templates) · NetWasm.Testing.VSTest(https://www.nuget.org/packages/NetWasm.Testing.VSTest) · NetWasm.Toolchain(https://www.nuget.org/packages/NetWasm.Toolchain) · NetWasm.Wit.Bindings.Tool(https://www.nuget.org/packages/NetWasm.Wit.Bindings.Tool)
已移植的库包(https://github.com/zion-sati/NetWasm.Libraries#packages) · TUnit 包(https://github.com/zion-sati/TUnit-NetWasm#netwasm-packages)
许可证
CoreLib、运行时库、模板和生成的辅助代码采用 MIT 许可,须保留上游声明。编译器和开发者工具链使用 NetWasm Community License 1.0。Compact 和 NetWasmGC 收集器采用 Apache-2.0。
Community License 对符合条件的个人、教育、开源工作、评估用途免费,对员工少于 250 人且年收入低于 1,000 万美元的组织免费。匹配的 GitHub Sponsors 等级(https://github.com/sponsors/zion-sati)授予有限的内部商业使用权;OEM 和商业产品使用需要单独协议。生成的应用可以使用其作者选择的许可证。不存在登录、激活、遥测或技术强制手段。
带上你的工作负载
从 Console.WriteLine(42) 开始,然后尝试最小的真实工作负载——那些部署体积、沙箱化或可移植性真正重要的场景。
如果缺少什么,请提交 issue(https://github.com/zion-sati/NetWasm/issues)。具体的工作负载是决定 1.0 支持边界的依据。
相似文章
最好的WebAssembly运行时可能仍然是根本没有运行时
本文使用支持宽算术的wasm2c对WebAssembly-to-C方法进行了基准测试,证明其在速度和内存使用方面仍然与专门的WebAssembly运行时(如Wasmer和Wasmtime)具有竞争力。
weblings:在 WASM 内部将 Rust 编译为 WASM
Weblings 是一个编译为 WebAssembly 的 Rust 编译器工具链,通过带有 playground 和 Rustlings 练习的网页 UI,支持在浏览器中直接编译和执行 Rust 代码。
2026 年 WebAssembly 运行时性能
本文使用 libsodium 加密库对多种 WebAssembly 运行时(WAVM、WasmEdge、WAMR、wasm2c、Wasmer、Wasmtime、Wazero、Node、Bun)进行了性能基准测试,比较了 2024、2025 和 2026 年的版本。结果显示,WAVM、WasmEdge(AOT)、WAMR(AOT)、wasm2c、Wasmer 和 Wasmtime 在 CPU 密集型加密任务中实现了接近原生的性能,而 wide_arithmetic 指令对加密代码有益。
我们的Rust到Zig重写进展如何
Roc编译器团队已将他们30万行Rust代码库重写为Zig,经过18个月实现了功能对等,生成了更小的WebAssembly二进制文件并提高了性能。
Wasmi 2.0 - 工程实现最快的Wasm解释器
Wasmi 2.0,一款WebAssembly解释器,已发布,带来了显著的性能提升,运行速度比1.0版本快约2.2倍,还新增了减小二进制大小和稳定燃料计量等功能。