Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
676 changes: 293 additions & 383 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,19 @@
"@semantic-release/git": "^11.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"@timohausmann/quadtree-ts": "^2.3.0",
"@types/node": "^26.5.1",
"@vitest/coverage-v8": "^4.1.11",
"@vitest/ui": "^4.1.11",
"@types/node": "^26.6.2",
"@vitest/coverage-v8": "^5.0.1",
"@vitest/ui": "^5.0.1",
"cross-env": "^10.1.0",
"flatbush": "^4.6.2",
"husky": "^9.1.7",
"lint-staged": "^17.5.1",
"oxlint": "^1.82.0",
"oxlint-tsgolint": "^7.0.2001",
"oxlint": "^1.85.0",
"oxlint-tsgolint": "^7.0.2003",
"pretty-bytes": "^7.1.3",
"semantic-release": "^25.0.9",
"typescript": "^7.0.2",
"vite": "^8.3.0",
"vitest": "^4.1.11"
"vite": "^8.3.1",
"vitest": "^5.0.1"
}
}
55 changes: 55 additions & 0 deletions src/__tests__/helpers/benchmark-suite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, test } from 'vitest';
import type { BenchFn, BenchFnOptions } from 'vitest';

interface BenchmarkRegistration {
name: string
fn: BenchFn
setup?: BenchmarkSetup
}

interface BenchmarkSetupTask {
opts: BenchFnOptions
}

type BenchmarkSetup = (task: BenchmarkSetupTask) => void;

export interface BenchmarkRegistrar {
(name: string, fn: BenchFn, options?: { setup: BenchmarkSetup }): void
skip(name: string, fn: BenchFn, options?: { setup: BenchmarkSetup }): void
}

export function benchmarkSuite(name: string, define: (bench: BenchmarkRegistrar) => void): void {
describe.each([name])('%s', () => {
const registrations: Array<BenchmarkRegistration> = [];
const bench = Object.assign(
(registrationName: string, fn: BenchFn, options?: { setup: BenchmarkSetup }) => {
registrations.push({ name: registrationName, fn, setup: options?.setup });
},
{
skip(registrationName: string) {
test.skip.each([registrationName])('%s', () => {
expect.unreachable();
});
},
},
);

define(bench);

test('benchmarks', { timeout: Math.max(60_000, registrations.length * 30_000) }, async ({ bench: createBench }) => {
const runnable = registrations.map(({ name: registrationName, fn, setup }) => {
const options: BenchFnOptions = {};
setup?.({ opts: options });
return Object.keys(options).length
? createBench(registrationName, options, fn)
: createBench(registrationName, fn);
});
expect(runnable.length).toBeGreaterThan(0);
if(runnable.length === 1) {
await runnable[0].run();
} else {
await createBench.compare(...runnable);
}
});
});
}
12 changes: 6 additions & 6 deletions src/__tests__/memory-buffer.bench.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bench, describe } from 'vitest';
import { benchmarkSuite } from './helpers/benchmark-suite';
import MemoryBuffer from '../memory-buffer';
import MemoryHeap from '../memory-heap';
import type AllocatedMemory from '../allocated-memory';
Expand All @@ -25,7 +25,7 @@ function fillBuffer() {
return { buffer, addrs };
}

describe(`MemoryBuffer: ${ALLOC_COUNT} allocations`, () => {
benchmarkSuite(`MemoryBuffer: ${ALLOC_COUNT} allocations`, (bench) => {
bench('malloc', () => {
const buffer = makeBuffer();
for(let i = 0; i < ALLOC_COUNT; i++) {
Expand Down Expand Up @@ -63,7 +63,7 @@ describe(`MemoryBuffer: ${ALLOC_COUNT} allocations`, () => {
});
});

describe(`MemoryBuffer: free ${ALLOC_COUNT} allocations`, () => {
benchmarkSuite(`MemoryBuffer: free ${ALLOC_COUNT} allocations`, (bench) => {
let buffer: MemoryBuffer;
let addrs: Array<number>;

Expand Down Expand Up @@ -115,7 +115,7 @@ describe(`MemoryBuffer: free ${ALLOC_COUNT} allocations`, () => {
// Large blocks where zero-filling dominates the per-allocation cost, so skipping it on fresh memory is visible.
const LARGE_ALLOC_COUNT = 100;
const LARGE_ALLOC_SIZE = 16 * 1024;
describe(`MemoryBuffer: ${LARGE_ALLOC_COUNT} large callocAs`, () => {
benchmarkSuite(`MemoryBuffer: ${LARGE_ALLOC_COUNT} large callocAs`, (bench) => {
bench('callocAs (fresh)', () => {
const buffer = makeBuffer();
for(let i = 0; i < LARGE_ALLOC_COUNT; i++) {
Expand Down Expand Up @@ -145,7 +145,7 @@ describe(`MemoryBuffer: ${LARGE_ALLOC_COUNT} large callocAs`, () => {
});

const CHURN_COUNT = 20_000;
describe(`MemoryBuffer: ${CHURN_COUNT} alloc/free churn`, () => {
benchmarkSuite(`MemoryBuffer: ${CHURN_COUNT} alloc/free churn`, (bench) => {
// Steady-state churn over a live set: half the ops allocate, half free a random live block.
bench('mixed alloc/free', () => {
const buffer = makeBuffer();
Expand All @@ -167,7 +167,7 @@ describe(`MemoryBuffer: ${CHURN_COUNT} alloc/free churn`, () => {
});

const HEAP_COUNT = 5_000;
describe(`MemoryHeap: ${HEAP_COUNT} alloc + free`, () => {
benchmarkSuite(`MemoryHeap: ${HEAP_COUNT} alloc + free`, (bench) => {
bench('allocUI32', () => {
const memory = new MemoryHeap({ bufferSize: BUFFER_SIZE });
for(let i = 0; i < HEAP_COUNT; i++) {
Expand Down
16 changes: 8 additions & 8 deletions src/__tests__/shared-data.bench.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bench } from 'vitest';
import { benchmarkSuite } from './helpers/benchmark-suite';
import SharedList from '../shared-list';
import MemoryHeap from '../memory-heap';
import SharedMap from '../shared-map';
Expand All @@ -8,7 +8,7 @@ import LocalPool from '../local-pool';
import SharedStack from '../shared-stack';

const ITERATE_COUNT = 10_000;
describe(`Shared Data Structures: ${ITERATE_COUNT} iterations`, () => {
benchmarkSuite(`Shared Data Structures: ${ITERATE_COUNT} iterations`, (bench) => {
let sharedList: SharedList;
bench('shared list', () => {
// eslint-disable-next-line
Expand Down Expand Up @@ -107,7 +107,7 @@ describe(`Shared Data Structures: ${ITERATE_COUNT} iterations`, () => {


const INDEX_COUNT = 1_000;
describe(`Shared Data Structures: ${INDEX_COUNT} indexed locations`, () => {
benchmarkSuite(`Shared Data Structures: ${INDEX_COUNT} indexed locations`, (bench) => {
let sharedVector: SharedVector;
bench('shared vector', () => {
for(let i = 0; i < INDEX_COUNT; i++) {
Expand Down Expand Up @@ -178,7 +178,7 @@ describe(`Shared Data Structures: ${INDEX_COUNT} indexed locations`, () => {
});

const INSERT_COUNT = 1_000;
describe(`Shared Data Structures: ${INSERT_COUNT} inserts`, () => {
benchmarkSuite(`Shared Data Structures: ${INSERT_COUNT} inserts`, (bench) => {
bench('shared list', () => {
let memory = new MemoryHeap();
let list = new SharedList(memory);
Expand Down Expand Up @@ -267,7 +267,7 @@ describe(`Shared Data Structures: ${INSERT_COUNT} inserts`, () => {
});

const DELETE_COUNT = 1_000;
describe(`Shared Data Structures: ${DELETE_COUNT} deletes end element`, () => {
benchmarkSuite(`Shared Data Structures: ${DELETE_COUNT} deletes end element`, (bench) => {
const LOCAL_INSERT_COUNT = 2_000;

let sharedList: SharedList;
Expand Down Expand Up @@ -355,7 +355,7 @@ describe(`Shared Data Structures: ${DELETE_COUNT} deletes end element`, () => {
});
});

describe(`Shared Data Structures: ${DELETE_COUNT} deletes random element`, () => {
benchmarkSuite(`Shared Data Structures: ${DELETE_COUNT} deletes random element`, (bench) => {
const LOCAL_INSERT_COUNT = 2_000;

let sharedList: SharedList;
Expand Down Expand Up @@ -444,7 +444,7 @@ describe(`Shared Data Structures: ${DELETE_COUNT} deletes random element`, () =>
});
});

describe(`Shared Data Structures: ${INSERT_COUNT} insert and deleting random elements`, () => {
benchmarkSuite(`Shared Data Structures: ${INSERT_COUNT} insert and deleting random elements`, (bench) => {
const RUN_COUNT = 2_000;

let sharedList: SharedList;
Expand Down Expand Up @@ -565,4 +565,4 @@ describe(`Shared Data Structures: ${INSERT_COUNT} insert and deleting random ele

function randomIndex(array: { length: number }) {
return Math.floor(Math.random() * array.length);
}
}
23 changes: 12 additions & 11 deletions src/__tests__/shared-map.bench.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { bench } from 'vitest';
import { benchmarkSuite } from './helpers/benchmark-suite';
import type { BenchmarkRegistrar } from './helpers/benchmark-suite';
import MemoryHeap from '../memory-heap';
import SharedMap from '../shared-map';

Expand All @@ -25,7 +26,7 @@ const implementations: Array<[string, Factory]> = [
];

// Builds and fills a fresh map before each sample so the timed body starts from the same populated state
function benchPopulated(name: string, factory: Factory, run: (map: MapLike) => void) {
function benchPopulated(bench: BenchmarkRegistrar, name: string, factory: Factory, run: (map: MapLike) => void) {
let map: MapLike;
bench(name, () => {
run(map);
Expand All @@ -41,7 +42,7 @@ function benchPopulated(name: string, factory: Factory, run: (map: MapLike) => v
});
}

describe(`SharedMap implementations: ${COUNT} sets`, () => {
benchmarkSuite(`SharedMap implementations: ${COUNT} sets`, (bench) => {
for(let [name, factory] of implementations) {
bench(name, () => {
let map = factory();
Expand All @@ -52,39 +53,39 @@ describe(`SharedMap implementations: ${COUNT} sets`, () => {
}
});

describe(`SharedMap implementations: ${COUNT} overwrites`, () => {
benchmarkSuite(`SharedMap implementations: ${COUNT} overwrites`, (bench) => {
for(let [name, factory] of implementations) {
benchPopulated(name, factory, (map) => {
benchPopulated(bench, name, factory, (map) => {
for(let i = 0; i < COUNT; i++) {
map.set(keys[i], values[i] + 1);
}
});
}
});

describe(`SharedMap implementations: ${COUNT} gets`, () => {
benchmarkSuite(`SharedMap implementations: ${COUNT} gets`, (bench) => {
for(let [name, factory] of implementations) {
benchPopulated(name, factory, (map) => {
benchPopulated(bench, name, factory, (map) => {
for(let i = 0; i < COUNT; i++) {
map.get(keys[i]);
}
});
}
});

describe(`SharedMap implementations: ${COUNT} deletes`, () => {
benchmarkSuite(`SharedMap implementations: ${COUNT} deletes`, (bench) => {
for(let [name, factory] of implementations) {
benchPopulated(name, factory, (map) => {
benchPopulated(bench, name, factory, (map) => {
for(let i = 0; i < COUNT; i++) {
map.delete(keys[i]);
}
});
}
});

describe(`SharedMap implementations: iterate ${COUNT} entries`, () => {
benchmarkSuite(`SharedMap implementations: iterate ${COUNT} entries`, (bench) => {
for(let [name, factory] of implementations) {
benchPopulated(name, factory, (map) => {
benchPopulated(bench, name, factory, (map) => {
// eslint-disable-next-line
for(let entry of map) {}
});
Expand Down
14 changes: 7 additions & 7 deletions src/spatial/__tests__/shared-spatial.bench.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bench, describe } from 'vitest';
import { benchmarkSuite } from '../../__tests__/helpers/benchmark-suite';
import { Quadtree as QuadtreeTs, Rectangle } from '@timohausmann/quadtree-ts';
import Flatbush from 'flatbush';
import MemoryHeap from '../../memory-heap';
Expand Down Expand Up @@ -118,7 +118,7 @@ function buildFlatbush(movedEntityCount = 0): Flatbush {
return index;
}

describe(`insert ${ENTITY_COUNT} entities individually`, () => {
benchmarkSuite(`insert ${ENTITY_COUNT} entities individually`, (bench) => {
bench('shared quadtree', () => {
buildShared();
});
Expand All @@ -136,7 +136,7 @@ describe(`insert ${ENTITY_COUNT} entities individually`, () => {
});
});

describe(`bulk insert ${ENTITY_COUNT} entities`, () => {
benchmarkSuite(`bulk insert ${ENTITY_COUNT} entities`, (bench) => {
bench('shared quadtree', () => {
buildSharedBulk();
});
Expand All @@ -154,7 +154,7 @@ describe(`bulk insert ${ENTITY_COUNT} entities`, () => {
});
});

describe(`${QUERY_COUNT} broad-phase queries`, () => {
benchmarkSuite(`${QUERY_COUNT} broad-phase queries`, (bench) => {
let sharedTree: SharedQuadtree;
let out: Array<number> = [];
bench('shared quadtree', () => {
Expand Down Expand Up @@ -216,7 +216,7 @@ describe(`${QUERY_COUNT} broad-phase queries`, () => {
});

function benchmarkNeighbors(label: string, maxResults: number) {
describe(`${QUERY_COUNT} ${label} nearest-neighbor queries`, () => {
benchmarkSuite(`${QUERY_COUNT} ${label} nearest-neighbor queries`, (bench) => {
let sharedTree: SharedQuadtree;
bench('shared quadtree', () => {
for(let query of queries) {
Expand Down Expand Up @@ -266,7 +266,7 @@ function benchmarkNeighbors(label: string, maxResults: number) {
benchmarkNeighbors('single', 1);
benchmarkNeighbors('ten', 10);

describe(`move ${MOVED_ENTITY_COUNT} of ${ENTITY_COUNT} entities one step`, () => {
benchmarkSuite(`move ${MOVED_ENTITY_COUNT} of ${ENTITY_COUNT} entities one step`, (bench) => {
let sharedTree: SharedQuadtree;
bench('shared quadtree', () => {
for(let i = 0; i < MOVED_ENTITY_COUNT; i++) {
Expand Down Expand Up @@ -308,7 +308,7 @@ describe(`move ${MOVED_ENTITY_COUNT} of ${ENTITY_COUNT} entities one step`, () =
});
});

describe(`move all ${ENTITY_COUNT} entities one step`, () => {
benchmarkSuite(`move all ${ENTITY_COUNT} entities one step`, (bench) => {
let sharedTree: SharedQuadtree;
bench('shared quadtree', () => {
for(let entity of entities) {
Expand Down
Loading