perf: more benchmarks

This commit is contained in:
KernelDeimos
2025-12-19 01:13:02 -05:00
committed by Eric Dubé
parent 75f3092077
commit d3c1fc3103
3 changed files with 469 additions and 0 deletions
@@ -0,0 +1,182 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { bench, describe } from 'vitest';
const { FileTracker } = require('./FileTracker');
// Helper to create a tracker with some access history
const createTrackerWithHistory = (accessCount) => {
const tracker = new FileTracker({ key: 'test-key', size: 1024 });
for ( let i = 0; i < accessCount; i++ ) {
tracker.touch();
}
return tracker;
};
describe('FileTracker - Construction', () => {
bench('create new FileTracker', () => {
new FileTracker({ key: `test-key-${ Math.random()}`, size: 1024 });
});
bench('create multiple FileTrackers', () => {
for ( let i = 0; i < 100; i++ ) {
new FileTracker({ key: `key-${i}`, size: i * 100 });
}
});
});
describe('FileTracker - touch() operation', () => {
bench('touch() on new tracker', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
}
});
bench('touch() with EWMA calculation', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
// Pre-warm with some touches
for ( let i = 0; i < 10; i++ ) {
tracker.touch();
}
// Benchmark steady-state touches
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
}
});
});
describe('FileTracker - score calculation', () => {
bench('score on fresh tracker', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
tracker.touch(); // Need at least one touch for meaningful score
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
bench('score on tracker with history (10 accesses)', () => {
const tracker = createTrackerWithHistory(10);
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
bench('score on tracker with history (100 accesses)', () => {
const tracker = createTrackerWithHistory(100);
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
}
});
});
describe('FileTracker - age calculation', () => {
bench('age getter', () => {
const tracker = new FileTracker({ key: 'test', size: 1024 });
for ( let i = 0; i < 10000; i++ ) {
void tracker.age;
}
});
});
describe('FileTracker - Cache eviction simulation', () => {
bench('compare scores of multiple trackers', () => {
// Simulate cache with 100 items
const trackers = [];
for ( let i = 0; i < 100; i++ ) {
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
// Simulate varying access patterns
const accessCount = Math.floor(Math.random() * 20);
for ( let j = 0; j < accessCount; j++ ) {
tracker.touch();
}
trackers.push(tracker);
}
// Find lowest score (eviction candidate)
for ( let i = 0; i < 100; i++ ) {
let minScore = Infinity;
let evictCandidate = null;
for ( const tracker of trackers ) {
const score = tracker.score;
if ( score < minScore ) {
minScore = score;
evictCandidate = tracker;
}
}
}
});
bench('sort trackers by score (eviction ordering)', () => {
const trackers = [];
for ( let i = 0; i < 50; i++ ) {
const tracker = new FileTracker({ key: `file-${i}`, size: i * 100 });
for ( let j = 0; j < i % 10; j++ ) {
tracker.touch();
}
trackers.push(tracker);
}
// Sort by score
for ( let i = 0; i < 10; i++ ) {
[...trackers].sort((a, b) => a.score - b.score);
}
});
});
describe('FileTracker - Real-world access patterns', () => {
bench('hot file pattern (frequent access)', () => {
const tracker = new FileTracker({ key: 'hot-file', size: 1024 });
for ( let i = 0; i < 1000; i++ ) {
tracker.touch();
if ( i % 10 === 0 ) {
void tracker.score;
}
}
});
bench('cold file pattern (rare access)', () => {
const tracker = new FileTracker({ key: 'cold-file', size: 1024 });
tracker.touch();
for ( let i = 0; i < 1000; i++ ) {
void tracker.score;
void tracker.age;
}
});
bench('mixed access with score checks', () => {
const trackers = [];
for ( let i = 0; i < 20; i++ ) {
trackers.push(new FileTracker({ key: `file-${i}`, size: 1024 }));
}
for ( let i = 0; i < 500; i++ ) {
// Random access
const idx = Math.floor(Math.random() * trackers.length);
trackers[idx].touch();
// Periodic eviction check
if ( i % 50 === 0 ) {
for ( const t of trackers ) {
void t.score;
}
}
}
});
});
+165
View File
@@ -0,0 +1,165 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { bench, describe } from 'vitest';
const { stringify_serializable_object, hash_serializable_object } = require('./datautil');
// Test data generators
const createFlatObject = (size) => {
const obj = {};
for ( let i = 0; i < size; i++ ) {
obj[`key${i}`] = `value${i}`;
}
return obj;
};
const createNestedObject = (depth, breadth) => {
if ( depth === 0 ) {
return { leaf: 'value' };
}
const obj = {};
for ( let i = 0; i < breadth; i++ ) {
obj[`level${depth}_child${i}`] = createNestedObject(depth - 1, breadth);
}
return obj;
};
const createMixedObject = () => ({
string: 'hello world',
number: 42,
boolean: true,
null: null,
array: [1, 2, 3, { nested: 'array' }],
nested: {
deep: {
value: 'found',
numbers: [1, 2, 3],
},
},
});
// Objects with different key orderings (should produce same hash)
const objA = { z: 1, a: 2, m: 3 };
const objB = { a: 2, m: 3, z: 1 };
const objC = { m: 3, z: 1, a: 2 };
describe('stringify_serializable_object - Flat objects', () => {
const small = createFlatObject(5);
const medium = createFlatObject(20);
const large = createFlatObject(100);
bench('small flat object (5 keys)', () => {
stringify_serializable_object(small);
});
bench('medium flat object (20 keys)', () => {
stringify_serializable_object(medium);
});
bench('large flat object (100 keys)', () => {
stringify_serializable_object(large);
});
});
describe('stringify_serializable_object - Nested objects', () => {
const shallow = createNestedObject(2, 3); // depth 2, 3 children each
const medium = createNestedObject(3, 3); // depth 3, 3 children each
const deep = createNestedObject(4, 2); // depth 4, 2 children each
bench('shallow nested (depth=2, breadth=3)', () => {
stringify_serializable_object(shallow);
});
bench('medium nested (depth=3, breadth=3)', () => {
stringify_serializable_object(medium);
});
bench('deep nested (depth=4, breadth=2)', () => {
stringify_serializable_object(deep);
});
});
describe('stringify_serializable_object - Mixed types', () => {
const mixed = createMixedObject();
bench('mixed type object', () => {
stringify_serializable_object(mixed);
});
bench('primitives', () => {
stringify_serializable_object('string');
stringify_serializable_object(42);
stringify_serializable_object(true);
stringify_serializable_object(null);
stringify_serializable_object(undefined);
});
});
describe('stringify_serializable_object - Key ordering normalization', () => {
bench('objects with different key orderings', () => {
// All should produce the same output
stringify_serializable_object(objA);
stringify_serializable_object(objB);
stringify_serializable_object(objC);
});
});
describe('stringify_serializable_object vs JSON.stringify', () => {
const obj = createFlatObject(20);
bench('stringify_serializable_object', () => {
stringify_serializable_object(obj);
});
bench('JSON.stringify (baseline, no key sorting)', () => {
JSON.stringify(obj);
});
bench('JSON.stringify with sorted keys (manual)', () => {
const sortedObj = {};
Object.keys(obj).sort().forEach(k => {
sortedObj[k] = obj[k];
});
JSON.stringify(sortedObj);
});
});
describe('hash_serializable_object', () => {
const small = createFlatObject(5);
const medium = createFlatObject(20);
const mixed = createMixedObject();
bench('hash small object', () => {
hash_serializable_object(small);
});
bench('hash medium object', () => {
hash_serializable_object(medium);
});
bench('hash mixed object', () => {
hash_serializable_object(mixed);
});
bench('hash objects with different key orderings (should be equal)', () => {
hash_serializable_object(objA);
hash_serializable_object(objB);
hash_serializable_object(objC);
});
});
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { bench, describe } from 'vitest';
const { EWMA, MovingMode, TimeWindow, normalize } = require('./opmath');
describe('EWMA - Exponential Weighted Moving Average', () => {
bench('EWMA put() with constant alpha', () => {
const ewma = new EWMA({ initial: 0, alpha: 0.2 });
for ( let i = 0; i < 1000; i++ ) {
ewma.put(Math.random() * 100);
}
});
bench('EWMA put() with function alpha', () => {
const ewma = new EWMA({ initial: 0, alpha: () => 0.2 });
for ( let i = 0; i < 1000; i++ ) {
ewma.put(Math.random() * 100);
}
});
bench('EWMA get() after many puts', () => {
const ewma = new EWMA({ initial: 0, alpha: 0.2 });
for ( let i = 0; i < 100; i++ ) {
ewma.put(i);
}
for ( let i = 0; i < 1000; i++ ) {
ewma.get();
}
});
});
describe('MovingMode - Mode calculation with sliding window', () => {
bench('MovingMode put() with window_size=30', () => {
const mode = new MovingMode({ initial: 0, window_size: 30 });
for ( let i = 0; i < 1000; i++ ) {
mode.put(Math.floor(Math.random() * 10));
}
});
bench('MovingMode put() with window_size=100', () => {
const mode = new MovingMode({ initial: 0, window_size: 100 });
for ( let i = 0; i < 1000; i++ ) {
mode.put(Math.floor(Math.random() * 10));
}
});
bench('MovingMode with high cardinality values', () => {
const mode = new MovingMode({ initial: 0, window_size: 50 });
for ( let i = 0; i < 1000; i++ ) {
mode.put(Math.floor(Math.random() * 1000));
}
});
bench('MovingMode with low cardinality values', () => {
const mode = new MovingMode({ initial: 0, window_size: 50 });
for ( let i = 0; i < 1000; i++ ) {
mode.put(Math.floor(Math.random() * 3));
}
});
});
describe('TimeWindow - Time-based sliding window', () => {
bench('TimeWindow add() and get()', () => {
let fakeTime = 0;
const tw = new TimeWindow({
window_duration: 1000,
reducer: values => values.reduce((a, b) => a + b, 0),
now: () => fakeTime,
});
for ( let i = 0; i < 1000; i++ ) {
fakeTime += 10;
tw.add(Math.random());
}
});
bench('TimeWindow with stale entry removal', () => {
let fakeTime = 0;
const tw = new TimeWindow({
window_duration: 100,
reducer: values => values.length,
now: () => fakeTime,
});
for ( let i = 0; i < 1000; i++ ) {
fakeTime += 50; // Fast time progression causes stale removal
tw.add(i);
tw.get();
}
});
});
describe('normalize - Exponential normalization', () => {
bench('normalize() single value', () => {
for ( let i = 0; i < 10000; i++ ) {
normalize({ high_value: 0.001 }, Math.random());
}
});
bench('normalize() with varying high_value', () => {
const high_values = [0.001, 0.01, 0.1, 1, 10];
for ( let i = 0; i < 10000; i++ ) {
const hv = high_values[i % high_values.length];
normalize({ high_value: hv }, Math.random() * 100);
}
});
});