Files
core/tests/engine/relation-store-read-perf.test.js
T

282 lines
9.5 KiB
JavaScript
Raw Normal View History

import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { RelationStore } from '../../src/core/RelationStore.js';
const runPerf = process.env.RUN_PERF_TESTS === '1';
const perfTest = runPerf ? test : test.skip;
describe('RelationStore Read Performance', () => {
perfTest('sequential read speed comparison', () => {
const numRelations = 100000;
const store = new RelationStore(numRelations);
// Add relations
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations);
}
// Benchmark sequential reads
const iterations = 10000;
const start1 = Date.now();
let sum1 = 0;
for (let i = 0; i < iterations; i++) {
const idx = i % store.size();
const rel = store.get(idx);
sum1 += rel.possibility;
}
const time1 = Date.now() - start1;
const start2 = Date.now();
let sum2 = 0;
for (let i = 0; i < iterations; i++) {
const idx = i % store.size();
const rel = store.getRaw(idx);
sum2 += rel.possibility;
}
const time2 = Date.now() - start2;
console.log(`Sequential read speed (${iterations} iterations):`);
console.log(` - get() (full object): ${time1}ms (${(time1/iterations*1000).toFixed(4)} µs/op)`);
console.log(` - getRaw() (typed arrays): ${time2}ms (${(time2/iterations*1000).toFixed(4)} µs/op)`);
console.log(` - Speedup: ${(time1/time2).toFixed(2)}x ${time2 < time1 ? '(faster)' : '(slower)'}`);
// Both should give same result
assert.ok(Math.abs(sum1 - sum2) < 1e-6);
});
perfTest('random access speed', () => {
const numRelations = 100000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations);
}
const iterations = 10000;
const indices = Array.from({length: iterations}, () =>
Math.floor(Math.random() * store.size())
);
const start1 = Date.now();
let count1 = 0;
for (const idx of indices) {
const rel = store.get(idx);
if (rel.possibility > 0.5) count1++;
}
const time1 = Date.now() - start1;
const start2 = Date.now();
let count2 = 0;
for (const idx of indices) {
const rel = store.getRaw(idx);
if (rel.possibility > 0.5) count2++;
}
const time2 = Date.now() - start2;
console.log(`Random access speed (${iterations} iterations):`);
console.log(` - get() (full object): ${time1}ms (${(time1/iterations*1000).toFixed(4)} µs/op)`);
console.log(` - getRaw() (typed arrays): ${time2}ms (${(time2/iterations*1000).toFixed(4)} µs/op)`);
console.log(` - Speedup: ${(time1/time2).toFixed(2)}x ${time2 < time1 ? '(faster)' : '(slower)'}`);
assert.strictEqual(count1, count2);
});
perfTest('findMatches performance', () => {
const numRelations = 50000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i % 1000, 'rel', (i + 1) % 1000);
}
const srcId = 500;
const relId = store.getRelationId('rel');
const iterations = 1000;
const start = Date.now();
for (let i = 0; i < iterations; i++) {
const matches = store.findMatches(srcId, relId, null);
}
const time = Date.now() - start;
console.log(`findMatches performance (${iterations} iterations):`);
console.log(` - Time: ${time}ms (${(time/iterations).toFixed(4)} ms/op)`);
console.log(` - Matches found: ${store.findMatches(srcId, relId, null).length}`);
console.log(` - Avg matches/query: ${(time/iterations).toFixed(4)}ms`);
assert.ok(time < iterations * 10, 'findMatches should be reasonably fast');
});
perfTest('forEach iteration speed', () => {
const numRelations = 100000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations, {
possibility: Math.random()
});
}
const start1 = Date.now();
let sum1 = 0;
store.forEach((rel) => {
sum1 += rel.possibility;
});
const time1 = Date.now() - start1;
const start2 = Date.now();
let sum2 = 0;
store.forEachRaw((rel) => {
sum2 += rel.possibility;
});
const time2 = Date.now() - start2;
console.log(`forEach iteration speed (${store.size()} relations):`);
console.log(` - forEach() (full object): ${time1}ms`);
console.log(` - forEachRaw() (typed arrays): ${time2}ms`);
console.log(` - Speedup: ${(time1/time2).toFixed(2)}x ${time2 < time1 ? '(faster)' : '(slower)'}`);
assert.ok(Math.abs(sum1 - sum2) < 1e-6);
});
perfTest('cache-friendly access pattern', () => {
const numRelations = 100000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations, {
possibility: Math.random(),
reliability: Math.random()
});
}
// Access all src fields first (cache-friendly)
const start1 = Date.now();
let totalSrc = 0;
for (let i = 0; i < store.size(); i++) {
totalSrc += store.src[i];
}
const time1 = Date.now() - start1;
// Access possibility fields (different typed array)
const start2 = Date.now();
let totalPoss = 0;
for (let i = 0; i < store.size(); i++) {
totalPoss += store.possibility[i];
}
const time2 = Date.now() - start2;
console.log(`Cache-friendly field access (${store.size()} relations):`);
console.log(` - src field access: ${time1}ms`);
console.log(` - possibility field access: ${time2}ms`);
console.log(` - Each operation: ${((time1+time2)/(store.size()*1000)).toFixed(4)} µs/element`);
});
perfTest('pattern matching queries', () => {
const numRelations = 50000;
const store = new RelationStore(numRelations);
// Create different relation types
const relationTypes = ['owner', 'member', 'viewer', 'editor'];
relationTypes.forEach(rel => store.getRelationId(rel));
for (let i = 0; i < numRelations; i++) {
const relType = relationTypes[i % relationTypes.length];
store.add(i, relType, (i + 1) % 1000, {
possibility: Math.random()
});
}
const queries = 1000;
// Query: Find all relations from a source
const srcId = 100;
const start1 = Date.now();
let count1 = 0;
for (let i = 0; i < queries; i++) {
const matches = store.findMatches(srcId, null, null);
count1 += matches.length;
}
const time1 = Date.now() - start1;
// Query: Find all relations of a specific type
const ownerRelId = store.getRelationId('owner');
const start2 = Date.now();
let count2 = 0;
for (let i = 0; i < queries; i++) {
const matches = store.findMatches(null, ownerRelId, null);
count2 += matches.length;
}
const time2 = Date.now() - start2;
console.log(`Pattern matching queries (${queries} queries):`);
console.log(` - Find by source: ${time1}ms (${(time1/queries).toFixed(4)} ms/query)`);
console.log(` - Find by relation type: ${time2}ms (${(time2/queries).toFixed(4)} ms/query)`);
console.log(` - Results: source=${count1/queries} avg, type=${count2/queries} avg`);
assert.ok(count1 > 0, 'Should find relations by source');
assert.ok(count2 > 0, 'Should find relations by type');
});
perfTest('update operation speed', () => {
const numRelations = 50000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations, {
possibility: 0.5,
reliability: 0.7
});
}
const updates = 10000;
const start = Date.now();
for (let i = 0; i < updates; i++) {
const idx = i % store.size();
store.update(idx, { possibility: 0.9 });
}
const time = Date.now() - start;
console.log(`Update operation speed (${updates} updates):`);
console.log(` - Time: ${time}ms`);
console.log(` - Avg per update: ${(time/updates).toFixed(4)} ms`);
console.log(` - Updates/sec: ${(updates/time*1000).toFixed(0)}`);
// Verify updates worked - check that value changed
const checkIdx = Math.floor(store.size() / 2);
const rel = store.get(checkIdx);
const beforeUpdate = store.get(0);
assert.ok(rel.possibility !== beforeUpdate.possibility || store.size() < 2, 'Update should change value');
});
perfTest('remove operation speed', () => {
const numRelations = 50000;
const store = new RelationStore(numRelations);
for (let i = 0; i < numRelations; i++) {
store.add(i, 'relation', (i + numRelations) % numRelations);
}
const initialSize = store.size();
const removes = 10000;
const start = Date.now();
for (let i = 0; i < removes; i++) {
const idx = Math.floor(Math.random() * store.size());
store.remove(idx);
}
const time = Date.now() - start;
const finalSize = store.size();
const actualRemoves = initialSize - finalSize;
console.log(`Remove operation speed (${removes} attempted):`);
console.log(` - Time: ${time}ms`);
console.log(` - Avg per remove: ${(time/removes).toFixed(4)} ms`);
console.log(` - Actual removes: ${actualRemoves} (deduplication)`);
console.log(` - Removes/sec: ${(actualRemoves/time*1000).toFixed(0)}`);
assert.strictEqual(finalSize, initialSize - removes);
});
});