NetWasm 0.10.0 使 C# 在 WebAssembly 体积上超越 Rust
C# 现在在 WebAssembly 体积上超越 Rust
要点: NetWasm 0.10.0 为简单的 print(42) 程序生成了 47,895 字节的独立 WASI 组件,比 Rust 1.95.0 的 size-profile 二进制文件(53,635 字节)小 10.7%,在 Brotli-11 压缩后小 19.5%。
原始和压缩体积对比
结果: NetWasm 的 Compact GC 构建原始体积为 47,895 字节,压缩后为 13,660 字节;Rust 的 size-profile 构建原始体积为 53,635 字节,压缩后为 16,963 字节。
| 工具链 | 原始 Wasm | Brotli-11 | 角色 |
|---|---|---|---|
| NetWasm, Compact GC | 47,895 B | 13,660 B | 内置精确 GC 的 WASI Preview 2 组件 |
| Rust 1.95.0, size profile | 53,635 B | 16,963 B | 匹配的 WASI Preview 2 组件 |
Kotlin 2.4.0 wasmWasi |
79,293 B | 22,655 B | Preview 1;宿主提供 GC |
| TinyGo 0.39.0 | 93,043 B | 26,931 B | Preview 1;自定义 GC |
两个二进制文件均使用默认发布设置生成,且没有手动后处理。NetWasm 的编译器流水线始终运行 Binaryen 的 wasm-opt,而 Rust 的构建不包含等效步骤,因为组件格式使重新编码变得复杂。
方法论说明
- Rust 构建使用
opt-level = "z"、LTO、单一 codegen 单元、panic = "abort"和剥离符号。 - NetWasm 的体积包括其运行时和精确的 Compact 垃圾收集器。
- 基准测试反映的是单个
print(42)工作负载,而非语言级别的体积排名。 - 两种工具链都可以通过
no_std(Rust)或零库方法(C#)进一步缩小,但这些配置会牺牲大部分标准库,对于 C# 还会牺牲垃圾收集器。
相对于 Mono AOT 的性能优势
要点: NetWasm 的分配和 GC 基准测试比从相同 C# 源码编译并在 Wasmtime 下执行的 Mono AOT 快 26-40%。
| 工作负载 | NetWasm (Compact GC) | Mono AOT (.NET 10) | 速度提升 |
|---|---|---|---|
| 完整事务 | 43.9 ms | 59.1 ms | 快 26% |
| 保留对象图 | 8.8 ms | 14.2 ms | 快 38% |
| UI 频繁更新 | 17.6 ms | 29.1 ms | 快 40% |
测量结果是 Apple M1 Max 上使用 Wasmtime 49.0.3 运行 15 次的中位数。NetWasm 在前两个工作负载上也优于桌面 .NET 11 JIT,尽管 JIT 在 UI 频繁更新上略快。Native AOT 整体上仍然最快。
超越 hello-world 的功能集
要点: NetWasm 支持功能完整的 C# 15 应用程序、JavaScript 互操作、静态 .a 归档、WIT 绑定、表达式树、HTTP/JSON/XML、无反射 DI,以及 wasm32 和 wasm64 目标。
- 通过标准
dotnetCLI 构建、运行、发布和测试。 - 为浏览器或 Web Workers 导出类型化的 JavaScript 模块。
- 链接带有生成的 C ABI 封送处理的本地
.a归档。 - 从 WIT 生成 C# 绑定,并生成独立的 WASI Preview 2 组件。
- 使用为 NetWasm 打包的 LINQ、HTTP、JSON、XML、Regex 和 DI 库。
- 为
netwasm0.1和桌面 .NET 提供双目标库。
该配置文件有意比桌面 .NET 小,但它已经支持完整的应用程序、可移植库、测试项目和浏览器模块。
快速入门指南
要点: 安装 .NET 10+ SDK 和 NetWasm 模板,即可在一分钟内编译并运行一个打印 42 的自包含 C# 程序。
# 安装 .NET SDK 10.0.303+(或 .NET 11 以支持 C# 15)
# 安装 NetWasm 模板
dotnet new install NetWasm.Templates
dotnet new netwasm-app -n HelloNetWasm
cd HelloNetWasm
dotnet restore
dotnet run # 打印 42
dotnet publish -c Release -o publish/local
# 使用 WASI Preview 2 宿主(例如 Wasmtime)运行
wasmtime run publish/local/HelloNetWasm.wasm
发布的组件相对于 .NET 是自包含的;运行时只需要一个 WASI 宿主。“使用 .NET 构建;无需 .NET 运行。”
NetWasm 如何保持小巧
要点: NetWasm 通过将可达的 CIL 直接编译为 Wasm、执行闭世界分析,并仅链接实际使用的运行时功能来实现其体积优势。
- 闭世界编译: 省略不可达代码、导入和运行时功能。
- 精简平台契约: 仅提供可移植 API;排除桌面运行时假设。
- 无广泛反射: 省略
dynamic、运行时程序集加载和通用类型检查等功能。 - 生成的边界: JSON、DI、WIT 和本地互操作使用静态契约而非运行时发现。
- 可选数据: 时区数据、丰富的堆栈跟踪和诊断仅在显式启用时包含。
- 单一收集器: 仅将选定的 GC 策略链接到最终二进制文件中。
这是一个专门构建的 .NET 平台,而不是被强行塞入 Wasm 沙箱的简化桌面运行时。
分配策略和体积权衡
要点: NetWasm 提供三种垃圾收集器策略,每种策略具有不同的体积和性能特征。
| 策略 | 最佳适用场景 | 原始组件 | Brotli-11 |
|---|---|---|---|
| Compact(默认) | 小型应用、低频繁更新 UI | 47,895 B | 13,660 B |
| NetWasmGC | 更高的分配吞吐量 | 50,855 B | 15,053 B |
| Bump | 有界、一次性任务 | 43,864 B | 11,959 B |
通过在项目文件中添加 <NetWasmGarbageCollector> 来选择策略;省略该属性则默认为 Compact。
库兼容性模型
要点: NetWasm 库多目标定位到 netwasm0.1 和桌面框架(例如 net10.0),使同一源代码可以在 Wasm 和传统 .NET 上运行。
# 创建一个同时针对两个平台的库
dotnet new netwasm-lib -n MyLibrary
生成的 NuGet 包包含两个运行时的资产,但只有符合 NetWasm 配置文件的库才能无需修改即可使用。
与传统 Blazor WebAssembly 的比较
要点: 打印 42 的 Blazor WebAssembly AOT 构建占用约 12 MiB(.NET 10)或约 15 MiB(.NET 11),使 NetWasm 大约大 256-319 倍。
| 构建 | 未压缩 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 的数字包括本地运行时和必需的程序集;NetWasm 包括其运行时、GC 和 WASI 组件。此比较是控制台组件与空 UI 的 Blazor 应用,而非完整框架的等效比较。
社区反馈亮点
“这有点不公平的比较,这里基准测试的 C# 代码也经过了后处理,而其他目标都没有。…… Rust 可以做得更小得多。” – @sondr3
“对我来说,C# 在后端方面就是感觉非常合适……良好的工具、现代语言特性、出色的调试体验。” – @CharlieDigital
“值得指出的是,这不是开源的,如果你在比较两者时这很重要的话(对我来说很重要)。” – @Scaled
“这有点不公平的比较,因为 WASM 自带垃圾收集器,C# 可以使用,而 Rust 自己管理所有内存,当然会更大。” – @weinzierl(后来阅读帖子后更正)
“不难…… Rust wasm 二进制文件巨大。如果你关心 WASM 性能、体积和内存使用,Zig 是更好的候选。” – @jedisct1
这些评论说明了主要的争议点:基准测试的公平性、开源许可,以及垃圾收集器开销的相关性。
当前状态和路线图
要点: NetWasm 处于 1.0 之前;0.10.0 版本引入了重建的垃圾收集器和面向体积的优化。未来的工作目标是更广泛的 WASI Preview 2 覆盖、可选的全球化数据、完整的表达式树支持、稳定的 netwasm1.0 契约和生产级诊断。
- 0.10.0 亮点: 对象上无收集器头、单分配数组、移除 Boehm 收集器、更快的分配密集型程序、减少时区数据的启动时间。
- 通往 1.0 之路: 扩展 WASI Preview 2 API 表面、启用更多 .NET Standard 2.1 库、稳定平台和运行时契约、改进增量开发循环。
该项目仍然欢迎贡献;问题和功能请求均受欢迎。
许可模式
要点: NetWasm 的核心库采用 MIT 许可;编译器和工具使用 NetWasm Community License 1.0,该许可对个人、教育、开源工作和小型组织(≤250 名员工,≤1000 万美元收入)免费。超出这些限制的商业使用需要单独协议。
- 运行时收集器(Compact、NetWasmGC)采用 Apache-2.0。
- 生成的应用程序中没有内置激活、遥测或技术强制。
包和生态系统
- 编译器和 SDK:
NetWasm.Compiler.Tasks、NetWasm.Sdk、NetWasm.Templates - 运行时:
NetWasm.Runtime.Wasm32、NetWasm.Runtime.Wasm64 - 托管:
NetWasm.Hosting、NetWasm.Hosting.Build - 库移植:
NetWasm.Libraries套件、TUnit-NetWasm
所有包均可从 NuGet 获取,并在仓库中有文档说明。