Skip to content

Make zipAttrsWith more parallel-eval friendly#16097

Open
edolstra wants to merge 1 commit into
masterfrom
zip-attrs-roots
Open

Make zipAttrsWith more parallel-eval friendly#16097
edolstra wants to merge 1 commit into
masterfrom
zip-attrs-roots

Conversation

@edolstra

@edolstra edolstra commented Jul 3, 2026

Copy link
Copy Markdown
Member

Motivation

The previous implementation used an std::map with traceable_allocator to collect the attributes, which is extremely expensive in parallel eval because every uncollectable alloc/free takes the global GC lock. The new implementation collects all the attributes in a vector (without the ListBuilder that was the reason why we needed traceable_allocator), sorts them by name, and then performs a single sweep through the vector to build one list at a time.

While this is mostly beneficial for parallel eval, it also seems to give a modest speedup for single-threaded eval (e.g. nix search nixpkgs fizzbuzz --no-eval-cache went from 28.9s to 28.5s).

Assisted-by: Claude Fable 5 noreply@anthropic.com

Taken from DeterminateSystems#534.

Context


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

The previous implementation used a std::map with traceable_allocator,
which costs one GC_MALLOC_UNCOLLECTABLE() and one GC_FREE() per
distinct attribute name. Uncollectable allocations always take the
global GC allocation lock, making this map a major source of mutex
contention during parallel evaluation (~12% of all futex calls in a
16-core 'nix search' run, plus the corresponding GC_free traffic).

Instead, collect the (name, value) pairs into a plain std::vector and
group them by stable-sorting on the name, which yields the same
attribute order and per-attribute value order as the map. The vector
doesn't need to be visible to the GC since the values it points to
are kept alive by the attrsets in args[1] for the duration of the
call.

At 16 eval cores (with GC disabled via GC_INITIAL_HEAP_SIZE=40G),
this cuts kernel time from ~21s to ~8-9s, context switches from 2.6M
to 1.5M, and elapsed time from ~6.2s to ~4.9s for 'nix search nixpkgs
--no-eval-cache fizzbuzz'. Single-core performance is unchanged.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant