initial import
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import fs from 'fs';
|
||||
|
||||
const results = [];
|
||||
|
||||
async function bench(name, fn, options = {}) {
|
||||
const iterations = options.iterations || 1;
|
||||
const warmup = options.warmup || 0;
|
||||
const unit = options.unit || 'ops/s';
|
||||
const itemsPerIter = options.itemsPerIter || null;
|
||||
|
||||
for (let i = 0; i < warmup; i++) {
|
||||
await fn();
|
||||
}
|
||||
|
||||
let totalTime = 0;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const start = process.hrtime.bigint();
|
||||
await fn();
|
||||
const end = process.hrtime.bigint();
|
||||
totalTime += Number(end - start) / 1e6;
|
||||
}
|
||||
|
||||
const avgTime = totalTime / iterations;
|
||||
const iterPerSec = 1000 / avgTime;
|
||||
const itemsPerSec = itemsPerIter ? Math.round(iterPerSec * itemsPerIter) : null;
|
||||
|
||||
const resultEntry = {
|
||||
name,
|
||||
avgTimeMs: parseFloat(avgTime.toFixed(3)),
|
||||
iterPerSec: Math.round(iterPerSec),
|
||||
itemsPerSec,
|
||||
unit,
|
||||
itemsPerIter,
|
||||
samples: iterations,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
results.push(resultEntry);
|
||||
|
||||
return resultEntry;
|
||||
}
|
||||
|
||||
async function run(options = {}) {
|
||||
const outputFile = options.output || './benchmark-results.json';
|
||||
const threshold = options.regressionThreshold || 0.9;
|
||||
|
||||
const output = {
|
||||
timestamp: new Date().toISOString(),
|
||||
package: process.env.npm_package_name || 'unknown',
|
||||
version: process.env.npm_package_version || '0.0.0',
|
||||
node: process.version,
|
||||
benchmarks: results
|
||||
};
|
||||
|
||||
if (fs.existsSync(outputFile)) {
|
||||
const previous = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
|
||||
if (previous.benchmarks) {
|
||||
for (const newBench of results) {
|
||||
const prev = previous.benchmarks.find(b => b.name === newBench.name);
|
||||
if (prev) {
|
||||
const ratio = newBench.iterPerSec / prev.iterPerSec;
|
||||
newBench.regression = ratio < threshold;
|
||||
newBench.comparedToPrevious = {
|
||||
iterPerSec: prev.iterPerSec,
|
||||
ratio: parseFloat(ratio.toFixed(4))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const regression = results.some(b => b.regression);
|
||||
|
||||
console.log('\nBenchmark Results:');
|
||||
console.log('-'.repeat(90));
|
||||
console.log(`${'Benchmark'.padEnd(50)} ${'Iter/s'.padStart(10)} ${'Items/s'.padStart(12)} Unit`);
|
||||
console.log('-'.repeat(90));
|
||||
for (const r of results) {
|
||||
const reg = r.regression ? ' [REGRESSION]' : '';
|
||||
const prev = r.comparedToPrevious ? ` (prev: ${r.comparedToPrevious.iterPerSec}, ratio: ${r.comparedToPrevious.ratio})` : '';
|
||||
const itemsRate = r.itemsPerSec ? r.itemsPerSec.toString().padStart(12) : ' -';
|
||||
console.log(`${r.name.padEnd(50)} ${r.iterPerSec.toString().padStart(10)} ${itemsRate} ${r.unit}${reg}${prev}`);
|
||||
}
|
||||
console.log('-'.repeat(90));
|
||||
|
||||
if (outputFile) {
|
||||
fs.writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
||||
console.log(`\nResults saved to: ${outputFile}`);
|
||||
}
|
||||
|
||||
if (regression) {
|
||||
console.error('\nWARNING: Performance regression detected!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export { bench, run };
|
||||
@@ -0,0 +1,84 @@
|
||||
import { bench, run } from './benchlib.js';
|
||||
import takeUntil from '../index.js';
|
||||
|
||||
function createSource() {
|
||||
let index = 0;
|
||||
let sink = null;
|
||||
return {
|
||||
pipe(s) { sink = s; return s; },
|
||||
resume() {
|
||||
while (sink && !sink.paused && index < 10000) {
|
||||
sink.write(index++);
|
||||
}
|
||||
},
|
||||
write() {},
|
||||
end() {},
|
||||
abort() {},
|
||||
get sink() { return sink; },
|
||||
get paused() { return sink ? sink.paused : true; }
|
||||
};
|
||||
}
|
||||
|
||||
function createCollectSink() {
|
||||
const items = [];
|
||||
return {
|
||||
items,
|
||||
write(data) { items.push(data); },
|
||||
end() {},
|
||||
abort() {},
|
||||
paused: false,
|
||||
ended: false
|
||||
};
|
||||
}
|
||||
|
||||
function createSignalStream() {
|
||||
let sink = null;
|
||||
return {
|
||||
pipe(s) { sink = s; return s; },
|
||||
write() {},
|
||||
end() { if (sink) sink.end(); },
|
||||
abort() {},
|
||||
get paused() { return false; },
|
||||
trigger() { if (sink) sink.end(); }
|
||||
};
|
||||
}
|
||||
|
||||
async function benchmark() {
|
||||
await bench('takeUntil with early signal (10000 items)', () => {
|
||||
const sink = createCollectSink();
|
||||
const signal = createSignalStream();
|
||||
const stream = takeUntil(signal);
|
||||
stream.pipe(sink);
|
||||
const source = createSource();
|
||||
source.pipe(stream);
|
||||
source.resume();
|
||||
signal.trigger();
|
||||
return sink.items.length;
|
||||
}, { iterations: 20, warmup: 2, itemsPerIter: 10000, unit: 'items/s' });
|
||||
|
||||
await bench('takeUntil with late signal (10000 items)', () => {
|
||||
const sink = createCollectSink();
|
||||
const signal = createSignalStream();
|
||||
const stream = takeUntil(signal);
|
||||
stream.pipe(sink);
|
||||
const source = createSource();
|
||||
source.pipe(stream);
|
||||
source.resume();
|
||||
return sink.items.length;
|
||||
}, { iterations: 20, warmup: 2, itemsPerIter: 10000, unit: 'items/s' });
|
||||
|
||||
await bench('takeUntil immediate signal (0 items)', () => {
|
||||
const sink = createCollectSink();
|
||||
const signal = createSignalStream();
|
||||
const stream = takeUntil(signal);
|
||||
stream.pipe(sink);
|
||||
const source = createSource();
|
||||
source.pipe(stream);
|
||||
signal.trigger();
|
||||
return sink.items.length;
|
||||
}, { iterations: 100, warmup: 5 });
|
||||
|
||||
await run({ output: './benchmark-results.json' });
|
||||
}
|
||||
|
||||
benchmark().catch(console.error);
|
||||
Reference in New Issue
Block a user