C# 现在在 WASM 体积上超越了 Rust
C# now beats Rust on WASM size

原始链接: https://github.com/zion-sati/NetWasm

NetWasm 是一个独立的 .NET 编译器、运行时和 SDK,可将 C# 和 .NET CIL 直接编译为小型独立 WebAssembly,无需附带桌面 CLR、Mono 或应用程序 DLL。它支持常规 dotnet CLI 工作流、已发布的 JS 模块/Web Workers、WIT/WASI Preview 2 组件、类型化互操作,以及 HTTP、JSON、XML、Regex、DI 等移植库。其紧凑的精确 GC 为 print-42 工作负载生成 47,798 字节的 WASI 组件,小于匹配的 Rust 配置,并且基准测试显示在某些工作负载上分配/GC 比 Mono AOT 更快。Blazor WebAssembly 大得多。NetWasm 提供多种 GC 策略、双目标定位、TUnit 测试以及 wasm32/wasm64 选项。它处于 1.0 前版本,有意采用更小的特性集:不支持广泛的运行时反射、动态加载或托管线程,且部分文件/套接字 API 尚未实现。核心库采用 MIT 许可;编译器/工具使用社区许可,符合条件的使用免费,并提供商业选项。使用 .NET 构建,使用 WebAssembly 主机运行。

Hacker News 上的一篇帖子声称 C# 现在在 WASM 二进制大小上超越了 Rust,并链接到一个 GitHub 项目。评论者认为比较不公平:C# 输出经过了后处理并针对 WASM 大小进行了优化,而 Rust 没有;而且 C# 无法使用 WASM 的 GC。尽管如此,几位用户觉得该项目令人印象深刻且很酷,认为偶尔出现 C# 的胜利值得欢迎。
相关文章

原文

Independent .NET for WebAssembly and WASI.
Write C#. Deploy small, standalone Wasm.

NetWasm is an independent compiler, CoreLib, runtime and SDK that compiles C# and .NET CIL directly to WebAssembly. It does not carry the desktop CLR, Mono, dotnet.js or application DLLs into production.

Star NetWasm on GitHub Publication License: Community + MIT

Compile C# to WebAssembly · Try it in the browser · Install and build · See supported APIs · Read the documentation

C# now beats Rust on Wasm size

NetWasm 0.8.0 + precise GC: 47,798 bytes
Rust 1.95.0 size-oriented profile: 53,635 bytes

For the same print-42 workload, NetWasm's result includes the runtime and precise Compact garbage collector. Both artifacts are standalone WASI Preview 2 Components built with size-oriented settings.

10.9% smaller raw. 20.4% smaller with Brotli-11.

Wasm footprint landscape with raw and Brotli-11 sizes. NetWasm with precise GC is 47,798 raw and 13,501 compressed bytes; Rust is 53,635 and 16,963; Kotlin/Wasm is 79,293 and 22,655; TinyGo is 93,043 and 26,931. NetWasm and Rust are the matched WASI Preview 2 comparison; Kotlin and TinyGo are contextual Preview 1 references.

Toolchain Raw Wasm Brotli 11 Comparison role
NetWasm 0.8.0, Compact GC 47,798 B 13,501 B WASI P2 Component; precise GC included
Rust 1.95.0, size profile 53,635 B 16,963 B Matched WASI P2 Component reference
Kotlin 2.4.0 wasmWasi 79,293 B 22,655 B P1 context; GC supplied by the host engine
TinyGo 0.39.0 93,043 B 26,931 B P1 context; precise GC configured

Rust is the headline comparison because it uses the same workload and output form. Kotlin/Wasm and TinyGo show the broader footprint landscape, but their targets and runtime boundaries differ. This is a measured workload/profile result, not a language-wide size ranking.

Exact sources, versions, settings and reproduction steps

As of 0.10.0, NetWasm runs our allocation and garbage-collection benchmarks faster than Mono AOT, compiled from the same C# source and run by the same Wasmtime:

Workload NetWasm 0.10.0, NetWasmGC Mono AOT, .NET 10 NetWasm time
Full transaction 43.9 ms 59.1 ms 26% less
Retained object graph 8.8 ms 14.2 ms 38% less
UI churn 17.6 ms 29.1 ms 40% less

These are medians of 15 runs on an Apple M1 Max under Wasmtime 49.0.3, each run checked against desktop .NET's result. Mono AOT is Microsoft's wasi-wasm target in its default Release configuration. NetWasmGC also takes less time than desktop .NET 11's JIT on the first two workloads (49.9 and 9.7 ms); the JIT is faster on UI churn (8.6 ms), and desktop NativeAOT is the fastest on all three.

Versions, settings, desktop figures and reproduction

The 46.7 KiB program is the baseline, not the boundary.

C# 15 · JavaScript modules and Web Workers · static .a interop · WIT/WASI P2 · expression trees · HTTP/JSON/XML/Regex · reflection-free DI · TUnit/VSTest · wasm64 · four allocation strategies

Today NetWasm can:

  • build, run, publish and test projects through the ordinary dotnet CLI;
  • publish C# exports as typed JavaScript modules that run in the page or in a Web Worker;
  • link compatible Wasm .a archives and generate bounded C ABI marshalling;
  • generate C# bindings from WIT and produce standalone WASI P2 Components;
  • construct and inspect expression trees without general runtime reflection;
  • use independently packaged LINQ, HTTP, JSON, XML, Regex, Hashing and dependency-injection libraries;
  • dual-target libraries for netwasm0.1 and desktop .NET.

The profile is deliberately smaller than desktop .NET. It already supports complete applications, portable libraries, test projects and browser modules.

Install .NET SDK 10.0.303 or newer, then run:

dotnet new install NetWasm.Templates
dotnet new netwasm-app -n HelloNetWasm
cd HelloNetWasm
dotnet restore
dotnet run
dotnet publish -c Release -o publish/local

Run the published component with a compatible WASI Preview 2 host, such as Wasmtime:

wasmtime run publish/local/HelloNetWasm.wasm

The published component is self-contained with respect to .NET. A WebAssembly host is still required. Build with .NET; run without .NET.

Projects using C# 15 select the .NET 11 SDK; earlier language versions can stay on .NET 10. The SDK restores its pinned Node, LLD and Binaryen tools through NuGet, so an ordinary app does not require a separate Emscripten, Git or Python installation. See the complete quickstart for host, restore, Windows, browser and dual-targeting details.

Roslyn produces CIL. NetWasm compiles the reachable program directly to Wasm, specializes its generics and links only the runtime support it needs. The linked Wasm component is the program.

  • Closed-world compilation. Unreachable code, imports and runtime features do not enter the artifact.
  • A smaller platform contract. NetWasm supports portable APIs instead of reproducing every desktop runtime assumption.
  • No broad runtime reflection. Runtime type names, dynamic assembly loading and the dynamic keyword are outside the profile.
  • Generated boundaries. JSON, DI, WIT and native interop use known contracts rather than runtime discovery.
  • Optional data and diagnostics. Timezone data, rich stack traces and other deployment data are included only when selected.
  • One collector. Only the chosen allocation strategy is linked.

This is a new .NET platform, not desktop .NET squeezed into a Wasm sandbox. That boundary is what makes native-Wasm-sized C# possible.

How JSON and optional features affect size

Choose your allocation strategy

Compact is the default: a size-first, precise, non-moving mark-and-sweep collector. It traces compiler-published roots and reference layouts without conservative scanning. Its name refers to implementation size, not moving heap compaction.

Strategy Best fit Raw component Brotli 11
Compact (default) Small applications and low-churn UI 47,895 B 13,660 B
NetWasmGC Higher allocation throughput 50,855 B 15,053 B
Bump Bounded, one-shot jobs 43,864 B 11,959 B

Select a strategy in the executable project's .csproj:

<PropertyGroup>
  <NetWasmGarbageCollector>NetWasm</NetWasmGarbageCollector>
</PropertyGroup>

Leaving the property unset selects Compact. Libraries remain collector-neutral; all four backends are packaged for wasm32 and wasm64. See Choosing a garbage collector for the tradeoffs and diagnostics.

Libraries should travel upward

A library written for the smaller NetWasm profile should also be useful on desktop .NET, rather than trapped in a Wasm-only ecosystem.

dotnet new netwasm-lib -n MyLibrary

The template targets netwasm0.1 and net10.0, producing NuGet assets for both platforms. This is explicit multi-targeting, not a promise that arbitrary desktop packages work in reverse.

NetWasm.Libraries provides the ported framework and extensions packages. TUnit-NetWasm provides ordinary dotnet test integration through the generic VSTest bridge.

How does this compare to conventional .NET Wasm?

For an empty-UI Blazor WebAssembly application that prints 42, with AOT, managed trimming, IL stripping, invariant globalization and size-focused native linking enabled:

AOT Wasm size comparison, lower is better: NetWasm with Compact GC 46.7 KiB, Blazor .NET 10 11.71 MiB, Blazor .NET 11 RC1 14.58 MiB. NetWasm is about 320 times smaller than the .NET 11 build by exact bytes.

AOT build Uncompressed Wasm Relative to NetWasm
NetWasm 0.8.0, Compact GC 47,798 B 1×
Blazor WebAssembly, .NET 10 12,276,684 B 257×
Blazor WebAssembly, .NET 11 RC1 15,289,241 B 320×

The Blazor total includes its native runtime and required Webcil assemblies; NetWasm includes its runtime, garbage collector and WASI component. This is a console component versus an empty-UI application, not an equivalent-framework comparison or a claim about every .NET workload.

Exact Blazor versions, settings and reproduction

NetWasm is pre-1.0. Its supported profile is usable today and covered by published qualification, but source, package, target-profile and ABI contracts may change between minor releases.

The normal portable output is a wasm32 WASI Preview 2 Component. Raw wasm32, raw wasm64 and JavaScript-host integration are advanced alternatives; wasm64 Components remain blocked by upstream tooling.

Runtime reflection, dynamic, runtime assembly loading and managed threading are outside the profile. General System.IO.File/Directory/FileStream, raw sockets and subprocess APIs are not yet implemented. Existing NuGet packages work only when their reachable code and API dependencies fit the supported profile.

Supported APIs and limitations · Documentation index

NetWasm 1.0 means a useful, documented and stable smaller platform. It does not mean “desktop .NET, but in Wasm.” The main remaining themes are:

  • broader WASI Preview 2 coverage;
  • globalization modes and optional culture data;
  • expression-tree completion;
  • a faster incremental development loop;
  • a stable netwasm1.0 platform and runtime contract;
  • production diagnostics and broader host qualification.

New in NetWasm 0.10.0: rebuilt garbage collectors. Objects carry no collector header, arrays are single allocations, NetWasmGC allocates by advancing a pointer, and Compact has a new core built for size and memory. Allocation-heavy programs run faster and use far less memory, and applications that ship time zone data start in a fraction of the time. The Boehm collector is removed. Release notes

In progress: broader compatibility with existing .NET Standard 2.1 library binaries and NuGet assets. The target is to consume libraries unchanged when their reachable code fits the NetWasm profile, without carrying unused unsupported paths into the application.

This does not turn NetWasm into desktop .NET. Reachable general runtime reflection, dynamic, runtime assembly loading, managed threading, unsupported host APIs and named-culture globalization remain outside the initial compatibility slice and fail at compile time.

Read the full 1.0 roadmap

Compiler, SDK, runtime and hosting packages

NetWasm.Compiler.Browser · NetWasm.Compiler.Tasks · NetWasm.Hosting · NetWasm.Hosting.Build · NetWasm.Ref · NetWasm.Runtime.Pack · NetWasm.Runtime.Wasm32 · NetWasm.Runtime.Wasm64 · NetWasm.Sdk · NetWasm.Templates · NetWasm.Testing.VSTest · NetWasm.Toolchain · NetWasm.Wit.Bindings.Tool

Ported-library packages · TUnit packages

CoreLib, runtime libraries, templates and generated support code are MIT, subject to preserved upstream notices. The compiler and developer tooling use the NetWasm Community License 1.0. The Compact and NetWasmGC collectors are Apache-2.0.

The Community License is free for qualifying individuals, education, open-source work, evaluation, and organizations with fewer than 250 employees and less than USD 10,000,000 in annual revenue. Matching GitHub Sponsors tiers grant limited internal commercial use; OEM and Commercial Offering use require a separate agreement. Generated applications may use their authors' chosen licenses. There is no sign-in, activation, telemetry or technical enforcement.

Licensing guide · Third-party notices · Commercial licensing: [email protected]

Start with Console.WriteLine(42), then try the smallest real workload where deployment size, sandboxing or portability matters. If something is missing, open an issue. Concrete workloads are how the 1.0 support boundary gets decided.

联系我们 contact @ memedata.com