Skip to content

Commit 1ebb01d

Browse files
committed
stress test
1 parent 10391af commit 1ebb01d

File tree

15 files changed

+741
-4
lines changed

15 files changed

+741
-4
lines changed

benches/apps/boids/src/world.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const world = createWorld(
66
Time,
77
SpatialHashMap(new SpatialHashMapImpl(5)),
88
BoidsConfig({
9-
initialCount: 500,
9+
initialCount: 4000,
1010
maxVelocity: 6,
1111
separationFactor: 16,
1212
alignmentFactor: 0.5,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Koota ECS Stress Test</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
</body>
12+
</html>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@app/stress-test",
3+
"private": true,
4+
"version": "0.0.1",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"koota": "workspace:*",
13+
"@sim/stress-test": "workspace:*"
14+
},
15+
"devDependencies": {
16+
"@config/typescript": "workspace:*",
17+
"vite": "catalog:"
18+
}
19+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import type { BenchResult } from '@sim/stress-test';
2+
import {
3+
benchMixedWorkload,
4+
benchMultiQueryPopulation,
5+
benchNotQueryChurn,
6+
benchQueryIteration,
7+
benchSpawnDestroy,
8+
benchTraitChurn,
9+
benchWideArchetype,
10+
} from '@sim/stress-test';
11+
12+
const ITERATIONS = 50;
13+
14+
type Suite = { name: string; run: () => BenchResult };
15+
16+
const suites: Suite[] = [
17+
{ name: 'spawn & destroy', run: () => benchSpawnDestroy(ITERATIONS) },
18+
{ name: 'trait churn', run: () => benchTraitChurn(ITERATIONS) },
19+
{ name: 'query iteration', run: () => benchQueryIteration(ITERATIONS) },
20+
{ name: 'multi-query population', run: () => benchMultiQueryPopulation(ITERATIONS) },
21+
{ name: 'wide archetype', run: () => benchWideArchetype(ITERATIONS) },
22+
{ name: 'not-query churn', run: () => benchNotQueryChurn(ITERATIONS) },
23+
{ name: 'mixed workload', run: () => benchMixedWorkload(ITERATIONS) },
24+
];
25+
26+
function fmtMs(v: number): string {
27+
if (v < 0.001) return `${(v * 1000).toFixed(2)} µs`;
28+
if (v < 1) return `${(v * 1000).toFixed(1)} µs`;
29+
return `${v.toFixed(3)} ms`;
30+
}
31+
32+
function createUI(): {
33+
setStatus: (text: string) => void;
34+
addResult: (r: BenchResult) => void;
35+
setDone: () => void;
36+
} {
37+
const app = document.getElementById('app')!;
38+
app.innerHTML = '';
39+
40+
const style = document.createElement('style');
41+
style.textContent = `
42+
* { margin: 0; padding: 0; box-sizing: border-box; }
43+
body { background: #0d1117; color: #c9d1d9; font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace; font-size: 14px; padding: 24px; }
44+
h1 { color: #58a6ff; font-size: 20px; margin-bottom: 4px; }
45+
.subtitle { color: #8b949e; font-size: 13px; margin-bottom: 20px; }
46+
#status { color: #f0883e; margin-bottom: 16px; font-size: 13px; }
47+
table { border-collapse: collapse; width: 100%; max-width: 1100px; }
48+
th { text-align: left; padding: 8px 12px; border-bottom: 2px solid #30363d; color: #8b949e; font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
49+
th.num { text-align: right; }
50+
td { padding: 8px 12px; border-bottom: 1px solid #21262d; }
51+
td.num { text-align: right; font-variant-numeric: tabular-nums; }
52+
td.name { color: #58a6ff; font-weight: 500; }
53+
tr:hover td { background: #161b22; }
54+
#run-btn { margin-top: 16px; padding: 8px 20px; background: #238636; color: #fff; border: none; border-radius: 6px; font-family: inherit; font-size: 14px; cursor: pointer; }
55+
#run-btn:hover { background: #2ea043; }
56+
#run-btn:disabled { background: #21262d; color: #484f58; cursor: not-allowed; }
57+
`;
58+
document.head.appendChild(style);
59+
60+
const h1 = document.createElement('h1');
61+
h1.textContent = 'Koota ECS Stress Test';
62+
app.appendChild(h1);
63+
64+
const sub = document.createElement('div');
65+
sub.className = 'subtitle';
66+
sub.textContent = `${ITERATIONS} iterations per benchmark · ${suites.length} benchmarks`;
67+
app.appendChild(sub);
68+
69+
const status = document.createElement('div');
70+
status.id = 'status';
71+
app.appendChild(status);
72+
73+
const table = document.createElement('table');
74+
const thead = document.createElement('thead');
75+
const headerRow = document.createElement('tr');
76+
const cols = ['Benchmark', 'Mean', 'Median', 'Min', 'Max', 'P99', 'P99.9', 'StdDev', 'ops/s'];
77+
for (const col of cols) {
78+
const th = document.createElement('th');
79+
th.textContent = col;
80+
if (col !== 'Benchmark') th.className = 'num';
81+
headerRow.appendChild(th);
82+
}
83+
thead.appendChild(headerRow);
84+
table.appendChild(thead);
85+
86+
const tbody = document.createElement('tbody');
87+
table.appendChild(tbody);
88+
app.appendChild(table);
89+
90+
const btn = document.createElement('button');
91+
btn.id = 'run-btn';
92+
btn.textContent = 'Run Again';
93+
btn.disabled = true;
94+
btn.onclick = () => {
95+
tbody.innerHTML = '';
96+
runAll();
97+
};
98+
app.appendChild(btn);
99+
100+
return {
101+
setStatus(text: string) {
102+
status.textContent = text;
103+
},
104+
addResult(r: BenchResult) {
105+
const tr = document.createElement('tr');
106+
const vals = [
107+
{ text: r.name, cls: 'name' },
108+
{ text: fmtMs(r.mean), cls: 'num' },
109+
{ text: fmtMs(r.median), cls: 'num' },
110+
{ text: fmtMs(r.min), cls: 'num' },
111+
{ text: fmtMs(r.max), cls: 'num' },
112+
{ text: fmtMs(r.p99), cls: 'num' },
113+
{ text: fmtMs(r.p999), cls: 'num' },
114+
{ text: fmtMs(r.stddev), cls: 'num' },
115+
{ text: (1000 / r.mean).toFixed(1), cls: 'num' },
116+
];
117+
for (const v of vals) {
118+
const td = document.createElement('td');
119+
td.textContent = v.text;
120+
td.className = v.cls;
121+
tr.appendChild(td);
122+
}
123+
tbody.appendChild(tr);
124+
},
125+
setDone() {
126+
status.textContent = 'Done.';
127+
btn.disabled = false;
128+
},
129+
};
130+
}
131+
132+
const ui = createUI();
133+
134+
function runAll() {
135+
const btn = document.getElementById('run-btn') as HTMLButtonElement;
136+
btn.disabled = true;
137+
138+
let idx = 0;
139+
140+
function next() {
141+
if (idx >= suites.length) {
142+
ui.setDone();
143+
return;
144+
}
145+
146+
const suite = suites[idx];
147+
ui.setStatus(`Running ${idx + 1}/${suites.length}: ${suite.name}...`);
148+
149+
setTimeout(() => {
150+
const result = suite.run();
151+
ui.addResult(result);
152+
idx++;
153+
next();
154+
}, 50);
155+
}
156+
157+
next();
158+
}
159+
160+
runAll();
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "@config/typescript/base.json",
3+
"include": ["src"]
4+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export const CONSTANTS = {
2-
BODIES: 40000,
2+
BODIES: 300000,
33
GRAVITY: -9.81 * 2,
44
FLOOR: -1000,
55
COMPONENTS: 100,
6-
MAX_COMPS_PER_ENTITY: 20,
6+
MAX_COMPS_PER_ENTITY: 50,
77
DRAIN: false,
88
};

benches/sims/n-body/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const NBODIES = 2000; // Number of entities
1+
const NBODIES = 10000; // Number of entities
22
const BASE_MASS = 0.1; // Base mass
33
const VAR_MASS = 0.8; // Amount of randomness added to mass
44
const INITIAL_C = 12000; // Mass used in calculation of orbital speed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@sim/stress-test",
3+
"private": true,
4+
"version": "0.0.1",
5+
"description": "Comprehensive ECS stress test targeting BitSet, TraitData[], Query[], and micro-optimizations",
6+
"type": "module",
7+
"main": "./src/index.ts",
8+
"dependencies": {
9+
"koota": "workspace:*"
10+
},
11+
"devDependencies": {
12+
"@config/typescript": "workspace:*"
13+
}
14+
}

0 commit comments

Comments
 (0)