Skip to content

Use talc as the guest heap allocator - #1928

Merged
ludfjig merged 2 commits into
mainfrom
ll/3-talc-allocator
Oct 30, 2026
Merged

ludfjig merged 2 commits into
mainfrom
ll/3-talc-allocator

Conversation

@ludfjig

@ludfjig ludfjig commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Switch the guest heap allocator from buddy_system_allocator to talc. buddy_system_allocator init writes a free-list pointer at the start of every power-of-two heap block. talc writes only the start and end of the heap, so guest init touches fewer heap pages.

talc runs behind a spin mutex, as talc recommends. spin is already a dependency.

Benchmarks

Compared to main. MSHV runs Linux 6.6.137.mshv2-5 and 6.6.100.mshv1-2. KVM runs Linux 6.17.

Benchmark MSHV 6.6.137 MSHV 6.6.100 KVM
sandboxes/create_initialized/small 3.09 ms → 2.11 ms (−32%) 2.68 ms → 2.63 ms (~) 2.65 ms → 2.44 ms (−8%)
sandboxes/create_initialized/medium 5.93 ms → 3.76 ms (−37%) 8.52 ms → 8.34 ms (~) 2.92 ms → 2.65 ms (−9%)
sandboxes/create_initialized/large 11 ms → 8.24 ms (−25%) 27.7 ms → 27.6 ms (~) 3.59 ms → 3.41 ms (−5%)
snapshot_files/cold_start_via_evolve/small 3.22 ms → 1.95 ms (−40%) 2.3 ms → 2.71 ms (~) 3.07 ms → 2.70 ms (−12%)

On 6.6.137 each first touch populates a 2 MiB chunk, and buddy's init touches 2 to 7 more chunks than talc. 6.6.100 is unchanged because it pins guest memory. Other benchmarks are unchanged.

Related to #343 but not closing it, as we might want to still evaluate mimalloc or snmalloc, which would require a bigger chunk of work

@ludfjig ludfjig added the kind/enhancement For PRs adding features, improving functionality, docs, tests, etc. label Oct 12, 2026
@ludfjig
ludfjig added this pull request to stack #1929 October 12, 2026 20:01
@hyperlight-gh-bot

This comment has been minimized.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 18:56
@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from 6919f7f to b364493 Compare October 13, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The unsafe heap claim lacks the required safety justification for its exclusive memory ownership invariant.

1 open finding
What changed in this PR

Switches the guest heap allocator to talc to reduce page touches during sandbox initialization.

Changes:

  • Replaces buddy_system_allocator with mutex-protected talc.
  • Updates dependency locks and changelog documentation.
File Description
src/​hyperlight_guest_bin/​src/​lib.rs Implements and initializes the talc guest allocator.
src/​hyperlight_guest_bin/​Cargo.toml Replaces the allocator dependency.
Cargo.lock Updates workspace dependencies.
src/​tests/​rust_guests/​Cargo.lock Updates guest test dependencies.
CHANGELOG.md Documents the performance improvement.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/hyperlight_guest_bin/src/lib.rs Resolved
@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from b364493 to c3a949a Compare October 13, 2026 21:01
@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from c3a949a to 844a1bf Compare October 13, 2026 23:00
@hyperlight-gh-bot

This comment has been minimized.

andreiltd
andreiltd previously approved these changes Oct 14, 2026

@andreiltd andreiltd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice! I wonder about using locking api rather than cell which talc seems to support as well? This would require unsafe impl Sync wrapper but would avoid atomic locking overhead?

Off-topic: there is an interesting article: https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html. The interrupts part I think is relevant to hyperligiht because interrupt handler can allocate while the interrupted code holds the heap lock.

@ludfjig

ludfjig commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Nice! I wonder about using locking api rather than cell which talc seems to support as well? This would require unsafe impl Sync wrapper but would avoid atomic locking overhead?

Off-topic: there is an interesting article: https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html. The interrupts part I think is relevant to hyperligiht because interrupt handler can allocate while the interrupted code holds the heap lock.

I tried the Cell option first, and it works, but decided to go with the lock instead to mimic current behavior (and I think deadlocking is slightly preferable to silent heap corruption?). The article was interesting indeed, we need to be careful we don't allocate heap in an exception handler

@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from b55b4ad to e92a004 Compare October 21, 2026 19:27
@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from e92a004 to ed6a16a Compare October 26, 2026 18:44
@ludfjig
ludfjig force-pushed the ll/3-talc-allocator branch from ed6a16a to 39f4ba1 Compare October 30, 2026 19:14
@hyperlight-gh-bot

This comment has been minimized.

Base automatically changed from ll/2-lazy-sandbox-memory to main October 30, 2026 22:13
buddy_system_allocator writes a free-list pointer at the start of every
power-of-two heap block during guest init. talc writes only the start
and end of the heap. The host populates snapshot memory on first guest
touch, so fewer written pages make sandbox creation faster.

Criterion sandboxes/create_initialized:

* MSHV: 25% to 37% faster for small to large heaps
* KVM: 9% to 11% faster for small and large heaps

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Add a SAFETY comment to the guest heap claim.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>

@jsturtevant jsturtevant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice improvement for such a small change, really interesting that the its so close in api!

@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: a8850fcb3f12
Baseline commit: d51a3f3b2075

kvm / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
374.16 µs (➖ 1.00x slower)
byte_chunks 766.94 ns (➖ 1.01x slower)
vec_bytes 624.54 ns (➖ 1.07x slower)

payload_allocation

slot_pool_segmented
65536 144.75 ns (➖ 1.02x slower)
262144 525.49 ns (➖ 1.23x faster)

sandboxes

create_initialized_and_drop
medium 13.24 ms (➖ 1.03x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
7.69 ns (➖ 1.01x faster) 7.68 ns (➖ 1.01x faster) 7.68 ns (➖ 1.01x faster)

snapshot_files

load_snapshot_unverified
small 119.40 µs (➖ 1.02x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 1.92 µs (➖ 1.08x faster) 2.04 µs (➖ 1.07x slower)
262144 9.44 µs (➖ 1.32x slower) 7.24 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.07 µs (➖ 1.06x slower) 1.03 µs (➖ 1.02x slower)
65536 6.15 µs (➖ 1.02x faster) 6.20 µs (➖ 1.05x faster)
262144 28.47 µs (➖ 1.05x slower)
kvm / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
670.61 µs (➖ 1.06x faster)
byte_chunks 705.46 ns (➖ 1.09x faster)
vec_bytes 521.89 ns (➖ 1.11x faster)

payload_allocation

slot_pool_segmented
65536 182.40 ns (➖ 1.05x faster)
262144 558.96 ns (➖ 1.06x faster)

sandboxes

create_initialized_and_drop
medium 15.34 ms (➖ 1.02x slower)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
7.16 ns (➖ 1.08x faster) 7.19 ns (➖ 1.07x faster) 7.20 ns (➖ 1.06x faster)

snapshot_files

load_snapshot_unverified
small 55.24 µs (➖ 1.14x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.24 µs (➖ 1.07x faster) 2.23 µs (➖ 1.07x faster)
262144 7.88 µs (➖ 1.06x faster) 7.84 µs (➖ 1.06x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 750.79 ns (➖ 1.04x faster) 750.02 ns (➖ 1.08x faster)
65536 7.48 µs (➖ 1.06x faster) 7.29 µs (➖ 1.06x faster)
262144 31.57 µs (➖ 1.03x faster)
mshv3 / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
360.64 µs (➖ 1.01x faster)
byte_chunks 960.85 ns (➖ 1.01x slower)
vec_bytes 710.02 ns (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
65536 193.14 ns (➖ 1.00x slower)
262144 731.14 ns (➖ 1.00x faster)

sandboxes

create_initialized_and_drop
medium 63.88 ms (➖ 1.00x slower)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
9.59 ns (➖ 1.05x faster) 9.53 ns (➖ 1.01x faster) 9.63 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 114.62 µs (➖ 1.08x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.21 µs (➖ 1.01x faster) 2.40 µs (➖ 1.03x faster)
262144 9.75 µs (➖ 1.04x slower) 10.06 µs (➖ 1.00x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.28 µs (➖ 1.05x slower) 1.25 µs (➖ 1.04x faster)
65536 8.43 µs (➖ 1.14x faster) 8.34 µs (➖ 1.16x faster)
262144 36.15 µs (➖ 1.06x faster)
mshv3 / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
666.12 µs (➖ 1.07x faster)
byte_chunks 934.61 ns (➖ 1.00x faster)
vec_bytes 652.54 ns (➖ 1.00x faster)

payload_allocation

slot_pool_segmented
65536 261.57 ns (➖ 1.08x slower)
262144 667.56 ns (➖ 1.03x faster)

sandboxes

create_initialized_and_drop
medium 77.76 ms (➖ 1.02x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
8.70 ns (➖ 1.00x slower) 8.73 ns (➖ 1.00x faster) 8.72 ns (➖ 1.00x slower)

snapshot_files

load_snapshot_unverified
small 56.08 µs (➖ 1.01x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.27 µs (➖ 1.01x faster) 2.26 µs (➖ 1.00x faster)
262144 7.89 µs (➖ 1.02x faster) 7.98 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 900.50 ns (➖ 1.03x faster) 898.35 ns (➖ 1.03x faster)
65536 7.49 µs (➖ 1.00x slower) 7.50 µs (➖ 1.01x faster)
262144 38.25 µs (➖ 1.04x faster)
hyperv-ws2025 / amd (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
2.09 ms (➖ 1.02x faster)
byte_chunks 1.21 µs (➖ 1.04x slower)
vec_bytes 784.04 ns (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
65536 231.19 ns (➖ 1.01x slower)
262144 784.22 ns (➖ 1.00x faster)

sandboxes

create_initialized_and_drop
medium 11.70 ms (➖ 1.10x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
10.04 ns (➖ 1.01x slower) 9.99 ns (➖ 1.00x slower) 9.91 ns (➖ 1.00x slower)

snapshot_files

load_snapshot_unverified
small 990.21 µs (➖ 1.09x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.35 µs (➖ 1.08x faster) 2.36 µs (➖ 1.00x slower)
262144 8.99 µs (➖ 1.13x faster) 9.36 µs (➖ 1.05x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.27 µs (➖ 1.05x faster) 1.27 µs (➖ 1.02x slower)
65536 8.71 µs (➖ 1.12x faster) 8.71 µs (➖ 1.03x faster)
262144 38.32 µs (➖ 1.02x faster)
hyperv-ws2025 / intel (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
3.01 ms (➖ 1.01x slower)
byte_chunks 1.17 µs (➖ 1.01x faster)
vec_bytes 761.35 ns (➖ 1.00x faster)

payload_allocation

slot_pool_segmented
65536 211.79 ns (➖ 1.01x slower)
262144 765.45 ns (➖ 1.21x faster)

sandboxes

create_initialized_and_drop
medium 12.41 ms (➖ 1.38x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
10.06 ns (➖ 1.00x slower) 9.72 ns (➖ 1.01x slower) 9.66 ns (➖ 1.00x slower)

snapshot_files

load_snapshot_unverified
small 602.19 µs (➖ 1.00x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.45 µs (➖ 1.06x slower) 2.29 µs (➖ 1.00x faster)
262144 7.45 µs (➖ 1.02x faster) 7.63 µs (➖ 1.02x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.08 µs (➖ 1.03x slower) 1.18 µs (➖ 1.11x slower)
65536 8.27 µs (➖ 1.01x faster) 8.28 µs (➖ 1.05x faster)
262144 40.76 µs (➖ 1.25x faster)

Reported by cargo ci bench-report --candidate run:41136703890 --config-file bench_report.toml.

@ludfjig
ludfjig merged commit 767aac2 into main Oct 30, 2026
81 of 86 checks passed
@ludfjig
ludfjig deleted the ll/3-talc-allocator branch October 30, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/enhancement For PRs adding features, improving functionality, docs, tests, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants