I have a lot of performance data about tartrazine since it’s a project with a very deterministic output on which I have done extensive performance work, and it’s very easy to benchmark.
I wanted to get a historic perf benchmark for the different releases, which all include static alpine-musl binaries.
Well, it was weird.
The blue lines are benchmarks of each release built on my machine. The red lines are benchmarks of each release using the static binaries from the release (lower is better).
See that performance loss? Static binaries got a lot slower at one point.
Of course this covers many months in time, so it could be many things:
- Crystal version
- Dependency versions
- Alpine version
After lots of compiling and comparing …
- In musl, static builds, crystal 1.21.0 is much slower than 1.20.1
- In glibc, dynamic builds, 1.21.0 and 1.20.1 perform the same
After debugging, the performance variation can be attributed to … garbage collection.
- In 1.21.0 + static + musl the heap size is “grow as you go”
- If I set it to start at 16MB then it’s fastest of all compiler versions I tried
| 1.21.0 static build, sqlite3.h → html | wall time |
|---|---|
| default GC settings | 94.7 ms |
| GC_INITIAL_HEAP_SIZE=16M | 49.2 ms |
| 1.20.1 default (control) | 54.5 ms |
This is a 50% reduction in wall time, entirely from changing a parameter!!! ![]()
- Not the dependencies
- Not the compiler version
- Not the operating system
The heap size.
While this is probably not universally applicable (do you really care about a 40ms startup shave?) it was super fun to figure out!
