HN Debrief

Don't use musl if you care about performance

  • Programming
  • Infrastructure
  • Open Source
  • Developer Tools

The post claims musl is a bad default if raw speed matters, even though it is popular for statically linked binaries, Rust deployment, Alpine-style small containers, and simpler packaging. The headline number in the article was a 26% slowdown on one application even after replacing musl’s allocator with mimalloc, and a much larger slowdown with musl’s own allocator, which pushed the conversation past the usual “just swap malloc” answer.

If you use musl mainly to get static binaries or smaller images, benchmark it against your real workload before standardizing on it. For multithreaded or allocation-heavy services, treat musl as a portability and packaging choice with a measurable throughput cost, not a free drop-in swap.

Discussion mood

Mostly agreeing with the post’s core warning. The mood was pragmatic rather than outraged: musl is useful for static binaries and tiny images, but people with performance experience said its allocator is notably weak under concurrency and that slower libc primitives still hurt even after swapping allocators.

Key insights

  1. 01

    Allocator contention is the real scaling limit

    The sharpest technical point was not that musl loses a few percent in microbenchmarks. It is that its allocator design puts a single global lock in the hot path, which creates visible contention even with modest thread counts and ordinary allocation patterns. That turns musl from a constant overhead into a scaling ceiling for services that should otherwise keep getting faster as cores increase.

    If your service uses worker pools or parallel request handling, run profiles that include allocator wait time before shipping a musl build. A libc choice that looks acceptable on one core can quietly cap throughput on many-core hosts.

      Attribution:
    • aseipp #1 #2
    • comex #1
  2. 02

    A better allocator only fixes part of it

    Replacing musl malloc with mimalloc or another high-performance allocator helps a lot, but it does not erase the gap. Commenters pointed to the article’s own numbers showing a large residual slowdown, and noted that some software still hits musl’s internal allocator anyway. That makes “just use mimalloc” a useful mitigation, not a full rebuttal.

    Do not treat an alternate allocator as a blanket sign-off for musl in production. Benchmark the full binary, including libc-heavy paths, instead of assuming malloc was the only problem.

      Attribution:
    • loeg #1 #2
    • masklinn #1
    • hibikir #1
  3. 03

    libc memcpy still beats compiler magic

    Several comments clarified why glibc’s low-level routines can stay ahead even when modern compilers know about memcpy and memset. Compilers inline small copies well, but libc can ship multiple hand-tuned implementations, pick the best one at runtime for the actual CPU, and avoid exploding code size by inlining giant fast paths everywhere. That is why “the compiler should optimize it anyway” does not make libc implementation quality irrelevant.

    If your workload moves a lot of memory, inspect library call behavior on target hardware instead of assuming compiler builtins have solved it. Runtime CPU dispatch and tuned assembly can still buy real wall-clock gains.

      Attribution:
    • compiler-guy #1
    • wahern #1
    • Joker_vD #1
    • SkiFire13 #1
    • fweimer #1
  4. 04

    musl persists because static Linux is awkward

    The packaging case for musl came through clearly. Teams want prebuilt binaries that run across Linux distributions, scratch images with minimal baggage, and freedom from glibc compatibility headaches. Commenters pointed at alternatives like Rust’s x86_64-unknown-linux-none target, relibc, and eyra, but also made clear these are limited, unsupported, or too immature for most mainstream applications. musl wins here because the other roads are rough.

    If you need one-file Linux distribution more than absolute speed, musl remains the path of least resistance. Just document that choice as a deployment optimization with technical limits, not as a universally better runtime base.

      Attribution:
    • plorkyeran #1
    • masklinn #1
    • VorpalWay #1 #2
    • lrvick #1

Against the grain

  1. 01

    Fix allocation patterns before blaming musl

    A credible minority argued that if allocator costs dominate, the program is already badly structured. In that view, hot loops should not be spraying small heap objects, and flattening data structures or using task-specific pools will dwarf any libc swap. That does not make musl fast, but it does mean some teams may be reaching for a runtime-level fix before taking the obvious application-level wins.

    If profiles show malloc near the top, spend at least one pass reducing allocation rate before making libc a major architectural decision. You may recover more performance with simpler data layout changes than with a new toolchain target.

      Attribution:
    • matherial #1
    • CyberDildonics #1 #2 #3
  2. 02

    Throughput is not the only performance metric

    Some comments pushed back on treating slower execution as the whole story. musl is often chosen for smaller binaries, simpler code, and sometimes lower memory use, and one commenter linked prior discussion arguing allocator speed comes with footprint tradeoffs. For IO-bound tools, utilities, and memory-constrained environments, that can be the right optimization target even if peak CPU throughput is worse.

    When evaluating musl, compare memory footprint, image size, and operational simplicity alongside requests per second. The right libc choice depends on which resource is expensive in your environment.

      Attribution:
    • bloppe #1
    • mattrighetti #1 #2 #3
    • masklinn #1
  3. 03

    A 26 percent hit may be acceptable

    Not everyone accepted the article’s framing that a 26% slowdown is automatically disqualifying. For some software that is an easy price to pay for easier shipping, while for others it becomes a huge infrastructure bill or breaks feasibility entirely. The useful adjustment is to stop arguing over adjectives like “terrible” and tie the number to workload economics.

    Translate benchmark deltas into dollars, latency budgets, or battery life before deciding. The same slowdown is noise in an internal admin tool and a showstopper in a saturated backend.

      Attribution:
    • marssaxman #1
    • SkiFire13 #1
    • wakawaka28 #1

In plain english

eyra
A Rust project that explores running on Linux without relying on a traditional C library.
glibc
The GNU C Library, the standard C library used by most Linux distributions.
IO-bound
Describes a program whose speed is limited mostly by input and output operations such as disk or network access rather than CPU work.
libc
The standard C library that provides core runtime functions like memory allocation, string handling, and system call wrappers.
malloc
A standard library function that allocates memory on the heap while a program is running.
memcpy
A standard C library function that copies a block of memory from one location to another.
memset
A standard C library function that fills a block of memory with a byte value.
mimalloc
A high-performance memory allocator developed by Microsoft that can replace a program’s default allocator.
musl
A lightweight standard C library often used in small, static, or security-focused Linux systems.
relibc
A C library implementation written in Rust, originally for Redox OS and also aiming to support Linux.
Rust
A systems programming language focused on memory safety and performance.
x86_64-unknown-linux-none
An experimental Rust target for Linux without the usual C library or standard runtime support.

Reference links

Alternative allocators and allocator behavior

Compiler and libc implementation details

Rust targets and libc alternatives

  • Rust target x86_64-unknown-linux-none
    Suggested as the cleanest path for fully self-contained Linux binaries without libc, though currently unsupported for most users.
  • eyra
    Mentioned as a project exploring Linux without a traditional libc in Rust.

Prior discussion