devenv hook-should-activate runs on every shell prompt and takes 70ms doing almost nothing.
That cost isn’t devenv’s, it’s nixpkgs’. Every program pays it: Nix gives each package its own store path and records a DT_RUNPATH with one dir per dependency, so the loader makes hundreds of failing openat() calls hunting for libraries. devenv version does ~486, imagemagick ~1225. It’s the decade old “stat storm.”
We compared five fixes at Tacosprint, each trading off LD_LIBRARY_PATH overrides, duplicate sonames, cross compilation, and disk:
Absolute DT_NEEDED paths: fastest, but loses LD_LIBRARY_PATH and the GPU driver swap.
RUNPATH symlink farm: keeps overrides, can’t handle duplicate sonames.
Per DSO ELF note cache: preserves everything, but needs a glibc patch.
Guix style ld.so.cache: proven, breaks cross compilation.
Static linking (musl): we spiked it, 70ms to 16ms, but only works for a self contained CLI like devenv.
We landed on the ELF note cache, revived after a decade in limbo:
patchelf --build-resolution-cache shipped in patchelf 0.19.0
nixpkgs#535735 turns it on across the whole package set
Longer term, we want to upstream the loader patch into glibc so every store based package manager, Guix included, benefits.
Full post: Making devenv start fast, and the whole nixpkgs with it - devenv
Umbrella issue: Address /nix/store recursive lookup/syscall overhead · Issue #481620 · NixOS/nixpkgs · GitHub
25 Likes
As this was posted as an announcement, I am wondering what the state of this is? The linked blog post mentions the release of patchelf 0.19. Will it be part of NixOS once it reaches unstable (currently it is on 0.15.2). What about the needed glibc patch?
Can anyone clarify?
3 Likes
As I was reading the list of options and trade-offs I told myself “I hope they chose the per ELF note cache option” and was thrilled to hear that that was the case. I don’t know anything about this, but it sounds like you made the right choice. Seems like taco-driven-development works! Thank you for this significant contribution.
This seems like the PR to follow:
staging ← domenkozar:glibc-resolution-cache
opened 04:51PM - 26 Jun 26 UTC
Removes the `/nix/store` dynamic linker "stat storm" (#481620): the loader searc… hes every `DT_RUNPATH` directory for every soname, producing thousands of failing `openat`/`stat` syscalls at process startup. This teaches glibc to read a per binary `.note.nixos.ldcache` `PT_NOTE` of pre resolved `DT_NEEDED` paths, written by `patchelf --build-resolution-cache`, and resolve a binary's direct dependencies straight from it. The note is consulted after `LD_LIBRARY_PATH` and before the `DT_RUNPATH` walk, so `LD_LIBRARY_PATH`, `LD_PRELOAD` and `/run/opengl-driver` overrides keep working.
See [Making devenv start fast, and the whole nixpkgs with it](https://devenv.sh/blog/2026/06/26/making-devenv-start-fast-and-the-whole-nixpkgs-with-it/) for in-depth explanation of all approaches considered.
### Measured (default on, patched glibc vs stock, same package versions)
| command | failing `openat` before | after | reduction |
| --- | --- | --- | --- |
| `magick --version` | 1233 | 8 | 99.4% |
| `devenv version` | 483 | 28 | 94% |
Residual opens are the irreducible glibc-hwcaps probes plus a few runtime `dlopen`s not recorded in the note. Full numbers and method in the blog post above.
### Why staging
This rebuilds the world:
* `ldcache.patch` is applied to the default glibc, so every binary runs under a note aware loader.
* A stdenv fixup hook runs `patchelf --build-resolution-cache` over each output's ELF files (registered from `preFixup` so it lands at the end of `postFixup`, after autoPatchelfHook has finalised the interpreter and RPATH; honours `dontPatchELF`; batches via `xargs`).
* The default `patchelf` is bumped 0.15.2 -> 0.19.0, which carries the writer and the relocation fixes that had kept it pinned.
### Commits
1. `patchelf: 0.15.2 -> 0.19.0` (standalone; happy to split into its own PR since the bump benefits everyone)
2. `glibc: read a per-DSO resolution-cache note and generate it by default`
### Disclosure
This pull request description and the changes were prepared with assistance from Claude (Claude Code, Claude Opus 4.8); the commits carry `Assisted-by` trailers per the automation/AI policy.
## Things done
- Built on platform:
- [x] x86_64-linux
- [ ] aarch64-linux
- [ ] x86_64-darwin
- [ ] aarch64-darwin
- Tested, as applicable:
- [ ] [NixOS tests] in [nixos/tests].
- [x] Tests in [lib/tests] or [pkgs/test] for functions and "core" functionality (`tests.glibc-resolution-cache`; patchelf 0.19.0 own suite passes 63/63).
- [ ] Ran `nixpkgs-review` on this PR. See [nixpkgs-review usage].
- [x] Tested basic functionality of all binary files, usually in `./result/bin/`.
- NixOS Release Notes
- [ ] Module update: when the change is significant.
- [x] Fits [CONTRIBUTING.md], [pkgs/README.md], [maintainers/README.md] and other READMEs.
- [x] Follows the [automation/AI policy].
[NixOS tests]: https://nixos.org/manual/nixos/unstable/index.html#sec-nixos-tests
[nixpkgs-review usage]: https://github.com/Mic92/nixpkgs-review#usage
[CONTRIBUTING.md]: https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md
[automation/AI policy]: https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#automationai-policy
[lib/tests]: https://github.com/NixOS/nixpkgs/blob/master/lib/tests
[maintainers/README.md]: https://github.com/NixOS/nixpkgs/blob/master/maintainers/README.md
[nixos/tests]: https://github.com/NixOS/nixpkgs/blob/master/nixos/tests
[pkgs/README.md]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/README.md
[pkgs/test]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/test
6 Likes