Skip to content

boehmgc: Batch GC_generic_malloc_many() block allocation#547

Merged
edolstra merged 2 commits into
mainfrom
batch-gc-malloc-many
Jul 9, 2026
Merged

boehmgc: Batch GC_generic_malloc_many() block allocation#547
edolstra merged 2 commits into
mainfrom
batch-gc-malloc-many

Conversation

@edolstra

@edolstra edolstra commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Motivation

This patches boehmgc to have GC_generic_malloc_many() (which refills the thread-local allocation caches) hand out more objects per call. The upstream version does only one page per call, but that's not a lot (~170 values). This causes high contention on the allocation lock.

Returning 64 pages each call reduces futex syscalls by 6x (3.58M to 0.58M) for nix search nixpkgs --no-eval-cache fizzbuzz, and reduces elapsed time at 24 cores from 4.5s to 4.0s.

Improvement over #546:

eval-cores-compare

Context

Summary by CodeRabbit

  • Bug Fixes
    • Improved allocation performance and reduced lock contention under heavy memory workloads by using batched heap-block allocation.
    • Added a tunable environment setting to control the “many blocks” batch size (defaulting to the new batching behavior); values are automatically clamped to safe limits.
    • On Linux, optionally requests transparent huge pages for mapped heap memory when enabled.
  • Chores
    • Updated the bundled build/patch configuration to enable the new batching behavior by default.

Add a patch to boehmgc that makes GC_generic_malloc_many() hand out up
to GC_many_blocks heap blocks worth of objects per acquisition of the
global GC allocation lock, instead of exactly one 4 KiB block. All
evaluator threads replenish their thread-local free lists (Values,
Envs, Bindings) through this function, so during parallel evaluation
they otherwise serialize on the allocation lock: at 12 eval cores,
batching 64 blocks reduces futex syscalls by 6x (3.58M to 0.58M) for
'nix search nixpkgs --no-eval-cache fizzbuzz', and reduces both
kernel time and elapsed time at higher core counts.

The batch size defaults to GC_MANY_BLOCKS_DEFAULT (set to 64 here)
and can be overridden at runtime via the GC_MALLOC_MANY_BLOCKS
environment variable (1-64, where 1 restores the upstream behavior).

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

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a boehmgc patch that batches GC_generic_malloc_many() across multiple heap blocks with a configurable batch size, and wires the patch plus a compile-time default into packaging/dependencies.nix.

Changes

GC Allocation Batching

Layer / File(s) Summary
GC generic malloc-many batching
packaging/patches/boehmgc-batch-malloc-many.patch
Adds GC_MANY_BLOCKS_DEFAULT, GC_MANY_BLOCKS_MAX, and GC_many_blocks, updates GC_generic_malloc_many() to allocate and chain multiple heap blocks per lock acquisition, and reads GC_MALLOC_MANY_BLOCKS during initialization.
Linux hugepage advice
packaging/patches/boehmgc-batch-malloc-many.patch
Adds an opt-in madvise(MADV_HUGEPAGE) path for mmap'd memory when GC_MADVISE_HUGEPAGES is present.
Nix package wiring
packaging/dependencies.nix
Adds the new patch to boehmgc's patches list and sets -DGC_MANY_BLOCKS_DEFAULT=64 in NIX_CFLAGS_COMPILE.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: cole-h

🚥 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 clearly matches the main change: batching GC_generic_malloc_many() block allocation in boehmgc.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch batch-gc-malloc-many

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packaging/patches/boehmgc-batch-malloc-many.patch (1)

37-37: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Apply the same batch-size clamp before splitting existing free lists.

The new-block path clamps GC_many_blocks to 1..GC_MANY_BLOCKS_MAX, but the existing free-list path uses the raw global. If GC_MANY_BLOCKS_DEFAULT is changed to an out-of-range value, this path can hand out a much larger batch than the allocator path.

♻️ Proposed fix
 int GC_many_blocks = GC_MANY_BLOCKS_DEFAULT;
+
+static int GC_effective_many_blocks(void)
+{
+    int nblocks = GC_many_blocks;
+    if (nblocks < 1) nblocks = 1;
+    if (nblocks > GC_MANY_BLOCKS_MAX) nblocks = GC_MANY_BLOCKS_MAX;
+    return nblocks;
+}
 
 ...
-          if ((word)my_bytes_allocd >= (word)GC_many_blocks * HBLKSIZE) {
+          if ((word)my_bytes_allocd >= (word)GC_effective_many_blocks() * HBLKSIZE) {
 ...
-        int nblocks = GC_many_blocks;
+        int nblocks = GC_effective_many_blocks();
         int i;
-
-        if (nblocks < 1) nblocks = 1;
-        if (nblocks > GC_MANY_BLOCKS_MAX) nblocks = GC_MANY_BLOCKS_MAX;

Also applies to: 54-58

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packaging/patches/boehmgc-batch-malloc-many.patch` at line 37, The
existing-free-list batching logic in the boehmgc patch still uses the raw
GC_many_blocks value, so it can diverge from the new-block path when the default
is out of range. Update the free-list splitting path that checks my_bytes_allocd
against GC_many_blocks * HBLKSIZE to apply the same 1..GC_MANY_BLOCKS_MAX clamp
used elsewhere before computing the batch size, keeping the behavior consistent
in the allocator’s batch-handout logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packaging/patches/boehmgc-batch-malloc-many.patch`:
- Line 37: The existing-free-list batching logic in the boehmgc patch still uses
the raw GC_many_blocks value, so it can diverge from the new-block path when the
default is out of range. Update the free-list splitting path that checks
my_bytes_allocd against GC_many_blocks * HBLKSIZE to apply the same
1..GC_MANY_BLOCKS_MAX clamp used elsewhere before computing the batch size,
keeping the behavior consistent in the allocator’s batch-handout logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3bcc7734-92e4-415a-a85f-874e0d3b1311

📥 Commits

Reviewing files that changed from the base of the PR and between 2f47be4 and e5999e7.

📒 Files selected for processing (2)
  • packaging/dependencies.nix
  • packaging/patches/boehmgc-batch-malloc-many.patch

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

@github-actions github-actions Bot temporarily deployed to pull request July 8, 2026 16:35 Inactive
Comment thread packaging/patches/boehmgc-batch-malloc-many.patch Outdated
@edolstra edolstra force-pushed the batch-gc-malloc-many branch from e5999e7 to aa68c2e Compare July 9, 2026 16:40
@edolstra edolstra enabled auto-merge July 9, 2026 16:41
cole-h
cole-h previously approved these changes Jul 9, 2026
@github-actions github-actions Bot temporarily deployed to pull request July 9, 2026 16:46 Inactive
Declare GC_MANY_BLOCKS_DEFAULT, GC_MANY_BLOCKS_MAX and GC_many_blocks
in private/gc_priv.h (following the GC_EXTERN convention for internal
globals) instead of defining them in mallocx.c and repeating the
constant 64 and an extern declaration in misc.c's environment
variable parsing.

Verified that the compile-time default still applies (futex calls
drop 6.6x vs GC_MALLOC_MANY_BLOCKS=1) and the environment variable
override still works.

Assisted-by: Claude Fable 5 <noreply@anthropic.com>
@edolstra edolstra force-pushed the batch-gc-malloc-many branch from aa68c2e to 7ed5959 Compare July 9, 2026 18:08
@github-actions github-actions Bot temporarily deployed to pull request July 9, 2026 18:14 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packaging/patches/boehmgc-batch-malloc-many.patch (1)

46-58: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reclaim-list path reads GC_many_blocks unclamped, unlike the block path.

Line 51 uses (word)GC_many_blocks * HBLKSIZE directly, but the block-allocation path (lines 68-72) clamps a local copy to [1, GC_MANY_BLOCKS_MAX]. The two paths therefore disagree if GC_many_blocks is ever outside that range — e.g. a compile-time -DGC_MANY_BLOCKS_DEFAULT set above GC_MANY_BLOCKS_MAX (the -D value is never validated) or set to 0 (block path floors to 1, reclaim path would break after a single object). Clamping GC_many_blocks once at initialization removes the divergence and lets you drop the per-call clamp in the block path.

♻️ Clamp once at init (misc.c)
         char * many_blocks_string = GETENV("GC_MALLOC_MANY_BLOCKS");
         if (many_blocks_string != NULL) {
           int many_blocks = atoi(many_blocks_string);
           if (many_blocks > 0 && many_blocks <= GC_MANY_BLOCKS_MAX)
             GC_many_blocks = many_blocks;
         }
+        if (GC_many_blocks < 1) GC_many_blocks = 1;
+        if (GC_many_blocks > GC_MANY_BLOCKS_MAX)
+          GC_many_blocks = GC_MANY_BLOCKS_MAX;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packaging/patches/boehmgc-batch-malloc-many.patch` around lines 46 - 58, The
reclaim-list branch in the GC allocation logic reads GC_many_blocks directly and
can diverge from the block-allocation branch, which already clamps to a safe
range. Update the initialization path that sets GC_many_blocks so it is clamped
once to [1, GC_MANY_BLOCKS_MAX], then rely on that normalized value in the
allocation code paths (including the reclaim-list check) to keep behavior
consistent and remove the need for repeated per-call clamping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packaging/patches/boehmgc-batch-malloc-many.patch`:
- Around line 46-58: The reclaim-list branch in the GC allocation logic reads
GC_many_blocks directly and can diverge from the block-allocation branch, which
already clamps to a safe range. Update the initialization path that sets
GC_many_blocks so it is clamped once to [1, GC_MANY_BLOCKS_MAX], then rely on
that normalized value in the allocation code paths (including the reclaim-list
check) to keep behavior consistent and remove the need for repeated per-call
clamping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 330a7df3-a161-45b4-a450-d3c872da57e7

📥 Commits

Reviewing files that changed from the base of the PR and between aa68c2e and 7ed5959.

📒 Files selected for processing (2)
  • packaging/dependencies.nix
  • packaging/patches/boehmgc-batch-malloc-many.patch
🚧 Files skipped from review as they are similar to previous changes (1)
  • packaging/dependencies.nix

@edolstra edolstra added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit 9b78439 Jul 9, 2026
33 checks passed
@edolstra edolstra deleted the batch-gc-malloc-many branch July 9, 2026 20:16
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