test: add comprehensive ECS benchmarks#21
Conversation
Add benchmarks for all performance-critical paths: Entity: creation with components, recycling (create/destroy cycles), archetype transitions (add/remove component). Query: Query1 (1K), Query2 (10K, 100K), Query3 (10K), Query4 (10K), Query2.Iter (10K). All measure iteration throughput via Each/Iter. Event bus: synchronous handler dispatch, asynchronous channel dispatch. Reflection: GetComponentValue and SetComponentValue reflection cost. World: Update loop at 10K and 100K entity scales. Component test type: add testDamage for Query4 benchmarks. Baseline results documented in PR description. Closes #12
There was a problem hiding this comment.
Pull request overview
Adds a comprehensive benchmark suite for the ECS package to enable consistent performance tracking (e.g., via benchstat) across entity lifecycle, archetype transitions, queries, event dispatch, reflection-based component access, and full-world updates.
Changes:
- Added benchmarks covering entity creation/recycling, archetype transitions, query iteration (including iterator-based), event bus dispatch, reflection component get/set, and world update at multiple scales.
- Introduced a new test-only component type (
testDamage) to support Query4 benchmarks.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| internal/ecs/component_test.go | Adds testDamage component type used by new benchmarks. |
| internal/ecs/benchmark_test.go | Adds a broad ECS benchmark suite for critical performance paths and larger-scale workloads. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| b.ResetTimer() | ||
| for i := range b.N { | ||
| e := entities[i%len(entities)] | ||
| if i%2 == 0 { | ||
| Set(w, e, testVelocity{DX: 1}) | ||
| } else { | ||
| Remove[testVelocity](w, e) | ||
| } |
There was a problem hiding this comment.
BenchmarkArchetypeTransition alternates Set/Remove based on the loop index, but the chosen entity also changes each iteration. That means the Remove branch usually targets entities that never had testVelocity, so it mostly exercises the early-return path (no archetype move) rather than a real transition. Consider toggling per-entity state (e.g., track whether each entity currently has testVelocity, or alternate Set/Remove on the same entity) so both add/remove paths actually move between archetypes.
| b.ResetTimer() | |
| for i := range b.N { | |
| e := entities[i%len(entities)] | |
| if i%2 == 0 { | |
| Set(w, e, testVelocity{DX: 1}) | |
| } else { | |
| Remove[testVelocity](w, e) | |
| } | |
| hasVelocity := make([]bool, len(entities)) | |
| b.ResetTimer() | |
| for i := range b.N { | |
| idx := i % len(entities) | |
| e := entities[idx] | |
| if hasVelocity[idx] { | |
| Remove[testVelocity](w, e) | |
| } else { | |
| Set(w, e, testVelocity{DX: 1}) | |
| } | |
| hasVelocity[idx] = !hasVelocity[idx] |
What changed and why
Establishes comprehensive benchmarks for all performance-critical ECS paths to enable data-driven optimization via
benchstat.New benchmarks
Baseline results (i5-9300H, linux/amd64)
How to test
go test -bench=. -benchmem ./internal/ecs/Related issue
Closes #12