Repository navigation
refactor(storage): replace 16 KiB chunks with per-archetype tables - #9
Merged
Merged
Conversation
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>
🚀 Deploying Preview to Cloudflare 🚀Preview URL: https://feat-step3-table-storage-archevolt.hayman.workers.dev (commit d26ee0e)This URL reflects your latest Preview deploymentPreview Deployments by commit
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
TablereplacesChunkPool,ChunkLayout, andNativeChunk. 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, andWorld.TrimExcess()releases it.EntityEntrydrops its chunk reference.SystemExecutionException.FirstChunk/ChunkCountbecomeFirstRow/RowCount.WithChunkBytesis gone with no replacement.ChunkViewbecomesBlockView, a row range of one table.net11.0moniker and runs nothing.scripts/compare-baselines.ps1compares twobaseline.ps1runs. 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.[Each], 256 B unread payload[Each], 256 B payload, 1,000,000[EachBlock]vector movement[EachBlock]vector movement now runs at 0.87 times Bevy'sfor_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.Nextout of line, the enumerator is address-exposed and each row goes through the stack. Small chunks madeNexthot enough to inline, and one table per archetype makes it cold. WithDOTNET_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. ForcingNextinline withAggressiveInliningfixed only the 1,000-entity case, so this PR does not include it.Verification
-warnaserror. New tests cover growth across reallocations withAdded/Changedticks, retained capacity, andTrimExcess.scripts/verify.ps1passes, including the consumer AOT and sample AOT stages.🤖 Generated with Claude Code