DEV Community

Cover image for How big is Hello World in C#? From 76 MB down to 0.73 MB
Naze Code
Naze Code

Posted on

How big is Hello World in C#? From 76 MB down to 0.73 MB

How big is the smallest C# program? It depends almost entirely on how you publish it. Same one line of code, five different dotnet publish commands, and the result ranges from a 76 MB folder of 191 files to a single 0.73 MB executable.

Machine: .NET SDK 10.0.302, Windows 11 x64. Sizes exclude .pdb files. Start time is the median of 21 runs of "start the process, print, exit", after a warm-up run.

The program under test:

Console.WriteLine("Hello, World!");
Enter fullscreen mode Exit fullscreen mode

The five ways it was published:

("1 default (needs .NET)",  ""),
("2 self-contained",        "--self-contained -r win-x64"),
("3 trimmed single file",   "--self-contained -r win-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true"),
("4 Native AOT",            "-r win-x64 -p:PublishAot=true"),
("5 Native AOT, tiny",      "-r win-x64 -p:PublishAot=true -p:OptimizationPreference=Size " +
                            "-p:InvariantGlobalization=true -p:UseSystemResourceKeys=true -p:StackTraceSupport=false"),
Enter fullscreen mode Exit fullscreen mode

(Each line is appended to dotnet publish Hello.csproj -c Release.) The results:

1 default (needs .NET)
  size:       0.16 MB, 4 files
  start+exit: 39 ms
2 self-contained
  size:       76.63 MB, 191 files
  start+exit: 40 ms
3 trimmed single file
  size:       12.30 MB, 1 files
  start+exit: 132 ms
4 Native AOT
  size:       1.06 MB, 1 files
  start+exit: 7 ms
5 Native AOT, tiny
  size:       0.73 MB, 1 files
  start+exit: 7 ms
Enter fullscreen mode Exit fullscreen mode

Sizes were identical on every run. Start times varied by a few milliseconds (for example 39–41 ms for the default, 132–141 ms for the trimmed single file), so treat them as "on my machine".

1. Default: 0.16 MB, but it isn't the whole story

The default publish is tiny because it contains only your code: Hello.dll, a small Hello.exe launcher and two config files. Everything else, the runtime and the base libraries, has to already be installed on the machine. That's a framework-dependent app. Great when you control the servers; a problem when you hand an .exe to someone without .NET.

2. Self-contained: 76.63 MB, 191 files

--self-contained ships the runtime with the app, so it runs on a machine with no .NET installed. The price: the whole runtime and base class library come along, 191 files for one line of code. Start time is about the same as the default, since it's the same runtime doing the same work.

3. Trimmed single file: 12.30 MB, one file

PublishTrimmed removes the parts of the libraries your code never uses, and PublishSingleFile bundles everything into one .exe. Down to 12 MB and one file.

Two catches:

  • Trimming can break code that uses reflection. The trimmer can't always see what will be loaded by name at runtime, so it may remove it. It warns you at publish time; take those warnings seriously.
  • In this test, this build started the slowest: about 130–140 ms, more than three times the default. I didn't dig into why, so I'll just report it: measure before choosing it for something that starts often, like a CLI tool.

4. Native AOT: 1.06 MB, 7 ms

PublishAot compiles everything ahead of time into native machine code. No separate runtime to load and no JIT compiling methods at startup. The result is a single 1 MB executable that started in 7 ms, about five times faster than the default.

The trade-off is that the program can't generate new code at runtime. Anything that relies on that (some reflection-heavy libraries, dynamic proxies, runtime code generation) needs to be AOT-compatible. On Windows, it also needs the Visual Studio C++ build tools installed to link.

5. Native AOT tuned for size: 0.73 MB

Four more switches, each giving up something a Hello World doesn't need:

  • OptimizationPreference=Size: tell the compiler to prefer smaller code over faster code.
  • InvariantGlobalization=true: drop culture-specific data (number and date formats per country). Fine here; not fine for an app that formats dates for users in many countries.
  • UseSystemResourceKeys=true: exception messages become resource keys instead of full text.
  • StackTraceSupport=false: less information in stack traces.

Result: 0.73 MB, about a hundred times smaller than the self-contained build, and still starting in 7 ms.

Which one should you use?

  • Web apps and services on servers you control: the default. Small deploys, and the runtime gets patched separately.
  • A tool you hand to people who may not have .NET: self-contained, or Native AOT if your dependencies support it.
  • CLI tools, serverless functions, containers where startup time matters: Native AOT is worth trying first.
  • The size-tuned switches: only when size really matters and you can live without what they remove.

Try the commands above on your machine and post your numbers in the comments. I'm curious how much start times vary.

I make short, tested videos about C# and what happens when you measure it. More on the Naze Code YouTube channel.


Sources: Microsoft Learn, .NET application publishing overview, Trim self-contained deployments, Native AOT deployment, Optimize AOT deployments. Tested on .NET SDK 10.0.302 (runtime 10.0.10), Windows 11 x64.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •

Official Platform Update

Security protocols have been updated for all developer accounts.

  • tr.ee/dev-to