Skip to content

Make zipAttrsWith more parallel-eval friendly#535

Merged
edolstra merged 1 commit into
mainfrom
improve-zipAttrsWith
Jul 6, 2026
Merged

Make zipAttrsWith more parallel-eval friendly#535
edolstra merged 1 commit into
mainfrom
improve-zipAttrsWith

Conversation

@edolstra

@edolstra edolstra commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

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 #534.

Context

Summary by CodeRabbit

  • Bug Fixes
    • Improved how attribute lists are combined, keeping values grouped consistently by attribute name.
    • Preserved the existing output format and ordering guarantees while making results more reliable for complex inputs.

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>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8359dbb-a4a6-4caf-85bd-6d59a127b067

📥 Commits

Reviewing files that changed from the base of the PR and between f065a9e and 92149d8.

📒 Files selected for processing (1)
  • src/libexpr/primops.cc

📝 Walkthrough

Walkthrough

The implementation of builtins.zipAttrsWith in src/libexpr/primops.cc was rewritten. It now collects attribute name/value pairs into a vector, stable-sorts by name, groups values per attribute, and applies the given function to each group, replacing the previous map-based counting and list-builder approach while preserving output structure.

Changes

zipAttrsWith Refactor

Layer / File(s) Summary
Vector-based grouping implementation
src/libexpr/primops.cc
Replaces the map-based counting/list-builder approach with a vector accumulation, stable sort by attribute name, grouping of values, and per-group function application to build the resulting attribute set.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: making zipAttrsWith friendlier to parallel evaluation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-zipAttrsWith

Comment @coderabbitai help to get the list of available commands.

@edolstra edolstra enabled auto-merge July 6, 2026 14:56
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

@github-actions github-actions Bot temporarily deployed to pull request July 6, 2026 14:57 Inactive
@edolstra edolstra added this pull request to the merge queue Jul 6, 2026
Merged via the queue into main with commit d32f199 Jul 6, 2026
33 checks passed
@edolstra edolstra deleted the improve-zipAttrsWith branch July 6, 2026 15:55
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.

2 participants