Compiled before it ships
Pinch or Ctrl+scroll to zoom; the text is selectable.
Source
·
TheYard
Compiled before it ships: the same container compiled by the runtime on its one shared core, and compiled ahead of time at the build
Compiled before it ships: the same container, compiled by the runtime and compiled ahead of time
Every box is a step; the path under each title is where that step lives. The plan has one core, shared by both sites, and every new container starts on it.
Compiled by the runtime: before 1.0.3.94
The image carries IL. Every method is compiled on the one core the first time it is called.
Compiled ahead of time: 1.0.3.94 on
The image carries machine code too. The runtime compiles again only what turns out hot.
The build
dotnet publish -c Release
The C# compiler writes intermediate language, IL: portable, and not
yet machine code. The image carries the IL for this app and its
packages, 221.1 MB in the registry with native files for every
platform the packages know.
IL only
The container starts
App Service, one instance, one core shared by both sites
The runtime loads the assemblies and finds IL. Nothing in them can run
until it is compiled, and the only compiler on the machine is the one
inside the running process.
load, then compile
Every method, compiled at its first call
the JIT, on the plan's one core
Each method is turned into machine code the first time it is called,
on the same core that is checking the schema, loading a hundred
thousand vehicles and answering the first visitors. 32,690 methods in
the first two minutes on 1.0.3.93, and 113 seconds of compiling
counted across every thread.
run, count, recompile
The hot methods, compiled again
tiered compilation with PGO
The runtime counts how often each method runs and compiles the hot
ones a second and a third time at full optimization. 62,432 methods by
the thirteenth minute, 66,685 by the seventy-fifth.
answer
The first visitors
/readyz, then the listing
Ready 79 seconds after the process started on 1.0.3.93, most of it
waits on the network that compiling cannot shorten; the compiling
shared the core with those waits and with the first answers.
The build
Dockerfile, region api-publish
Restore for linux-x64 with ReadyToRun asked for, which fetches the
ahead-of-time compiler. Publish compiles this app and its packages to
machine code for that platform and keeps the IL beside it. One switch;
nothing in the code changes. 206.2 MB in the registry, 15 MB smaller,
because one platform needs one set of native files.
IL and machine code
The container starts
App Service, the same plan
The runtime loads the assemblies and finds machine code already there
for this app, EF Core, the Cosmos DB SDK and Identity. The framework
in the aspnet base image shipped this way already.
load, then run
Runs as compiled
ReadyToRun code, the first tier
A method that arrived compiled runs without a visit to the JIT. The
runtime compiled 12,273 to 16,525 methods itself at the first read
across four rolls, 49 to 62 per cent fewer than before.
count, recompile the hot ones
The hot methods, compiled again
TieredCompilation and TieredPGO, api/TheYard.Api/TheYard.Api.csproj
Still on. Compiled-ahead code is the first tier; the methods that turn
out hot are compiled again with instruments and then at full
optimization, so a warmed process gives up nothing. That second
compile is why the time spent compiling did not fall with the count.
answer, and report
The first visitors
/api/admin/metrics, region runtime-metrics
The reading that says what happened: methods the runtime compiled
itself and its time compiling, beside the collector's mode. Ready read
71 to 161 seconds across the four rolls, on a core the plan's meter
showed at 85 to 99 per cent from before the containers began.
The dashed line is the work that moved: from the container's first minutes to the build.
What held across four rolls: 49 to 62 per cent fewer methods compiled by the runtime at the first read, every time. What did not: no faster start measured, on a core the meter showed saturated through every roll.
What would settle it: the same load in Docker on one machine with the plan's limits and nothing else on the core, compiled ahead of time and not.
Source: docs/images/compiled-ahead.svg in the repository, drawn by docs/images/compiled-ahead.mjs and redrawn when a step moves.