Making Jetzig project compilation over 10000% faster

Compile Optimization

Currently Jetzig projects take quite a long time to compile (somewhere around 15-20 seconds). Waiting for a project to recompile after each change is frustrating.

This blog post covers the two strategies taken to reduce compilation speed down to less than 200ms.

All figures are shown using this website’s codebase to demonstrate compilation of a “real” application. My PC is around ten years old, my CPU is: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz.

Current Implementation

Jetzig currently compiles using LLVM. Zig’s self-hosted compiler is a little incomplete so compiling without LLVM triggers various compiler errors (specifically errors relating to SIMD features).

The very first build of a Jetzig project is very slow:

$ time zig build
real    0m24.161s
user    0m22.910s
sys     0m1.801s

Once the first build is complete, subsequent builds are quicker but still very slow:

$ time zig build
real    0m16.100s
user    0m15.274s
sys     0m1.160s

Waiting for a server to recompile for 16 seconds every time a change is made is not fun.

Optimization 1: Switch to Zig’s self-hosted compiler

Zig’s self-hosted compiler skips the LLVM step entirely. This step is by far the slowest stage of the build process.

The main issue we have is the Zig compiler’s missing implementations for @Vector SIMD operations.

Luckily these are only used in a few of Jetzig’s dependencies:

Copying from Zig’s stdlib I made a few pull requests (thanks to Karl for merging the PRs on Christmas Day).

After these changes, we were able to use Zig’s self-hosted compiler to build a project:

$ time zig build
real    0m13.688s
user    0m14.593s
sys     0m1.273s

Even the first-time build is faster than an incremental build using LLVM, but the important figure is the incremental re-builds, where most developers spend their time waiting:

$ time zig build
real    0m5.922s
user    0m7.325s
sys     0m0.624s

Around 4-6 seconds is a lot more bearable and is definitely a huge reduction without too much work, but we can do better.

Optimization 2: Use Zig’s incremental compilation

Zig recently added incremental compilation to its nightly build. This allows us to only compile modified code.

After a few changes to Jetzig’s build pipeline and running Zig with --watch -fincremental we get a build time of 139ms.

Build Summary: 14/14 steps succeeded
install success
└─ install jetzig.dev success
   └─ zig build-exe jetzig.dev Debug native success 139ms
      ├─ run routes (routes.zig) success 12ms MaxRSS:6M
      └─ run routes (routes.zig) (+1 more reused dependencies)

None of the above figures are cherry-picked - we got a real Jetzig application to rebuild from 16 seconds down to 5.9 seconds down to 139ms.

Next Steps

Currently there are two main issues:

  • Reloading generated files (e.g. the routes file or Zmpl’s template manifest)
  • Dependencies that require LLVM (i.e. they use features not yet implemented in the Zig compiler)

The first issue I’m hopeful that I can find a workaround and then I can merge the changes into main.

As Zig matures we should see the second issue disappear but, for now, LLVM is likely going to be needed in some capacity for most real-world projects.

Once the changes are merged we can look at browser hot-reloading which could allow developers to make a change to a Jetzig project and see the changes auto-reload in the browser within 200ms.

11 Likes

Why real < user + sys? More than one thread on some stages?

Yes, docs here show calculation for user time.

I included full output for each command for completeness but real time is the one I care about most.

1 Like