Skip to content

refactor(storage): replace 16 KiB chunks with per-archetype tables - #9

Merged
Stephanvs merged 5 commits into
mainfrom
feat/step3-table-storage
Oct 28, 2026
Merged

Stephanvs merged 5 commits into
mainfrom
feat/step3-table-storage

Conversation

@Stephanvs

Copy link
Copy Markdown
Owner

Why

ROADMAP step 3. With chunks, an unread wide component shrinks every span a hot query sees, and every structural move pays per-chunk bookkeeping. One slab per archetype gives a serial query one span per table.

Scope

  • Table replaces ChunkPool, ChunkLayout, and NativeChunk. Each archetype owns one 64-byte-aligned slab: entity keys, value columns, and tick columns for tracked components. Capacity doubles from 8 rows. Memory is not zero-filled, and every write path initialises a new row before anything reads it. An emptied table keeps its slab, and World.TrimExcess() releases it.
  • EntityEntry drops its chunk reference.
  • Parallel runs split the matched rows into contiguous ranges. SystemExecutionException.FirstChunk/ChunkCount become FirstRow/RowCount. WithChunkBytes is gone with no replacement.
  • ChunkView becomes BlockView, a row range of one table.
  • BenchmarkDotNet moves to 0.16.0-preview.2. Version 0.15.8 throws on the net11.0 moniker and runs nothing.
  • scripts/compare-baselines.ps1 compares two baseline.ps1 runs. The traversal matrix gains the named-query case that the step 3 gate names.

Results

Full tables are in benchmarks/step3-table-storage.md. Both runs used the same machine, the same .NET 11 RC2 build, and clean worktrees, run back to back.

Case, 100,000 entities unless noted Before µs After µs Ratio
Tag round trip 29,130 9,893 0.34
Wide round trip 31,570 11,270 0.36
Despawn 6,168 4,929 0.80
[Each], 256 B unread payload 174.8 98.47 0.56
[Each], 256 B payload, 1,000,000 4,809 1,020 0.21
[EachBlock] vector movement 28.74 20.62 0.72
Named query, 256 B payload 161.5 198.9 1.23
Named query, 1,000, no payload 1.180 2.065 1.75

[EachBlock] vector movement now runs at 0.87 times Bevy's for_each, at 23.78 µs.

Gate

The gate is not fully met. Tag round trip passes, and every storage test passes. Named queries regress at 1,000 entities with one archetype and at 100,000. The Tier-1 listings show why. When tiered PGO leaves QueryCursor.Next out of line, the enumerator is address-exposed and each row goes through the stack. Small chunks made Next hot enough to inline, and one table per archetype makes it cold. With DOTNET_TieredPGO=0, both sides compile the same code, and tables are never slower. The worst cases there are 0.71x and 0.39x. Step 2 rewrites this enumerator, so its gate now also requires beating the chunked named-query numbers. Forcing Next inline with AggressiveInlining fixed only the 1,000-entity case, so this PR does not include it.

Verification

  • 155 of 155 tests pass, and the build has 0 warnings with -warnaserror. New tests cover growth across reallocations with Added/Changed ticks, retained capacity, and TrimExcess.
  • scripts/verify.ps1 passes, including the consumer AOT and sample AOT stages.
  • A deliberate break that skips the tick copy during growth fails the new growth test.

🤖 Generated with Claude Code

Stephanvs and others added 5 commits October 27, 2026 22:14
Step 3's gate compares the named query over an unread 256-byte payload
with the chunked baseline. The traversal matrix measured only [Each].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BenchmarkDotNet 0.15.8 cannot run on .NET 11. Its SDK validator throws
NotImplementedException from GetRuntimeVersion for the unrecognised
net11.0 runtime moniker, so no benchmark starts. 0.16.0-preview.2 runs
on 11.0.0-rc.2 and exports the same JSON fields scripts/baseline.ps1
reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each roadmap step lands with before and after numbers. The script joins
two baseline.ps1 summaries and marks each case faster, slower, or within
the before range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each archetype now owns one Table: a single 64-byte-aligned native slab
holding the entity column and every value and tick column, doubling when
full and never cleared. Tables keep their capacity when emptied;
World.TrimExcess() releases it. NativeBytes sums table allocations.

Parallel Each partitions by contiguous row ranges across matched tables,
so a serial query sees one block per non-empty table. ChunkView becomes
BlockView, SystemExecutionException reports FirstRow/RowCount, and the
generators emit `block` locals.

Removes ChunkPool, ChunkLayout, NativeChunk, the directory's chunk
reference, and WorldBuilder.WithChunkBytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before and after runs on .NET 11 RC2 from clean worktrees. Tag and wide
round trips run at 0.34-0.36x and [EachBlock] movement now beats Bevy's
for_each. Named queries regress at 1,000 and 100,000 entities. Tier-1
listings show the cause is the enumerator staying on the stack when PGO
leaves QueryCursor.Next out of line. With TieredPGO off, both sides get
the same code and the tables are never slower. Step 2's gate now
requires the named query to beat its chunked numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-step3-table-storage-archevolt.hayman.workers.dev (commit d26ee0e)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://782ee692-archevolt.hayman.workers.dev d26ee0e 2026-10-28T00:52:55.906Z Visit the dashboard ↗

@Stephanvs
Stephanvs merged commit e949384 into main Oct 28, 2026
2 checks passed
@Stephanvs
Stephanvs deleted the feat/step3-table-storage branch October 28, 2026 08:37
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