Skip to content

Commit

Permalink
refactor(lib): make the code usable outside/inside node core
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Nov 23, 2023
1 parent 8ff60b1 commit c60c80e
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 125 deletions.
53 changes: 17 additions & 36 deletions lib/clock.js → lib/clock.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
'use strict';

const {
FunctionPrototypeBind,
MathMax,
NumberPrototypeToFixed,
Symbol,
Number,
globalThis,
} = primordials;

const {
codes: { ERR_INVALID_STATE },
} = require('internal/errors');
const { validateInteger, validateNumber } = require('internal/validators');

let debugBench = require('internal/util/debuglog').debuglog('benchmark', (fn) => {
import { debug } from 'util';
import { FunctionPrototypeBind, MathMax, NumberPrototypeToFixed } from './primordials.mjs';

export let debugBench = debug('benchmark', (fn) => {
debugBench = fn;
});

const kUnmanagedTimerResult = Symbol('kUnmanagedTimerResult');

// If the smallest time measurement is 1ns
// the minimum resolution of this timer is 0.5
const MIN_RESOLUTION = 0.5;
export const MIN_RESOLUTION = 0.5;

class Timer {
constructor() {
Expand All @@ -42,7 +29,7 @@ class Timer {
* @returns {string}
*/
format(timeInNs) {
validateNumber(timeInNs, 'timeInNs', 0);
// validateNumber(timeInNs, 'timeInNs', 0);

if (timeInNs > 1e9) {
return `${NumberPrototypeToFixed(timeInNs / 1e9, 2)}s`;
Expand All @@ -64,9 +51,9 @@ class Timer {
}
}

const timer = new Timer();
export const timer = new Timer();

class ManagedTimer {
export class ManagedTimer {
/**
* @type {bigint}
*/
Expand Down Expand Up @@ -113,17 +100,17 @@ class ManagedTimer {
end(iterations = 1) {
this.#end = timer.now();

validateInteger(iterations, 'iterations', 1);
// validateInteger(iterations, 'iterations', 1);

this.#iterations = iterations;
}

[kUnmanagedTimerResult]() {
if (this.#start === undefined)
throw new ERR_INVALID_STATE('You forgot to call .start()');
throw new Error('You forgot to call .start()');

if (this.#end === undefined)
throw new ERR_INVALID_STATE('You forgot to call .end(count)');
throw new Error('You forgot to call .end(count)');

return [Number(this.#end - this.#start), this.#iterations];
}
Expand All @@ -149,8 +136,10 @@ return timer[kUnmanagedTimerResult]();
`;
}

const AsyncFunction = async function() { }.constructor;
const SyncFunction = function() { }.constructor;
const AsyncFunction = async function () {
}.constructor;
const SyncFunction = function () {
}.constructor;

function createRunner(bench, recommendedCount) {
const isAsync = bench.fn.constructor === AsyncFunction;
Expand All @@ -167,15 +156,14 @@ function createRunner(bench, recommendedCount) {

const selectedTimer = hasArg ? new ManagedTimer(recommendedCount) : timer;

const runner = FunctionPrototypeBind(
compiledFn, globalThis, bench, selectedTimer, recommendedCount, kUnmanagedTimerResult);
const runner = FunctionPrototypeBind(compiledFn, globalThis, bench, selectedTimer, recommendedCount, kUnmanagedTimerResult);

debugBench(`Created compiled benchmark, hasArg=${hasArg}, isAsync=${isAsync}, recommendedCount=${recommendedCount}`);

return runner;
}

async function clockBenchmark(bench, recommendedCount) {
export async function clockBenchmark(bench, recommendedCount) {
const runner = createRunner(bench, recommendedCount);
const result = await runner();

Expand All @@ -186,10 +174,3 @@ async function clockBenchmark(bench, recommendedCount) {

return result;
}

module.exports = {
clockBenchmark,
debugBench,
timer,
MIN_RESOLUTION,
};
46 changes: 11 additions & 35 deletions lib/histogram.js → lib/histogram.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
'use strict';

const { validateNumber } = require('internal/validators');
const { codes: { ERR_OUT_OF_RANGE } } = require('internal/errors');
const {
MathMin,
MathMax,
MathCeil,
MathSqrt,
MathPow,
MathFloor,
NumberMAX_SAFE_INTEGER,
ArrayPrototypeSort,
ArrayPrototypePush,
ArrayPrototypeFilter,
ArrayPrototypeReduce,
ArrayPrototypeSlice,
NumberIsNaN,
Symbol,
} = primordials;

const kStatisticalHistogramRecord = Symbol('kStatisticalHistogramRecord');
const kStatisticalHistogramFinish = Symbol('kStatisticalHistogramFinish');

class StatisticalHistogram {
import { ArrayPrototypeFilter, ArrayPrototypePush, ArrayPrototypeReduce, ArrayPrototypeSort, MathCeil, MathFloor, MathMax, MathMin, MathPow, MathSqrt, NumberMAX_SAFE_INTEGER } from './primordials.mjs';
import { validateNumber } from './validators.mjs';

export const kStatisticalHistogramRecord = Symbol('kStatisticalHistogramRecord');
export const kStatisticalHistogramFinish = Symbol('kStatisticalHistogramFinish');

export class StatisticalHistogram {
/**
* @type {number[]}
* @default []
Expand All @@ -49,7 +31,7 @@ class StatisticalHistogram {
* @returns {number[]}
*/
get samples() {
return ArrayPrototypeSlice(this.#all);
return this.#all.slice();
}

/**
Expand Down Expand Up @@ -87,16 +69,16 @@ class StatisticalHistogram {
percentile(percentile) {
validateNumber(percentile, 'percentile');

if (NumberIsNaN(percentile) || percentile < 0 || percentile > 100)
throw new ERR_OUT_OF_RANGE('percentile', '>= 0 && <= 100', percentile);
if (Number.isNaN(percentile) || percentile < 0 || percentile > 100)
throw new Error('Invalid percentile value. Must be a number between 0 and 100.');

if (this.#all.length === 0)
return 0;

if (percentile === 0)
return this.#all[0];

return this.#all[MathCeil(this.#all.length * (percentile / 100)) - 1];
return this.#all[Math.ceil(this.#all.length * (percentile / 100)) - 1];
}

/**
Expand Down Expand Up @@ -198,9 +180,3 @@ class StatisticalHistogram {
this.#cv = stddev / this.#mean;
}
}

module.exports = {
StatisticalHistogram,
kStatisticalHistogramRecord,
kStatisticalHistogramFinish,
};
18 changes: 6 additions & 12 deletions lib/runner.js → lib/index.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
'use strict';

const { ArrayPrototypePush } = primordials;
const { debugBench, timer } = require('internal/benchmark/clock');
const { reportConsoleBench } = require('internal/benchmark/report');
const { maxTime, minTime, runBenchmark, getInitialIterations } = require('internal/benchmark/lifecycle');
const { validateNumber, validateString, validateFunction, validateObject } = require('internal/validators');
import { reportConsoleBench } from './report.mjs';
import { ArrayPrototypePush } from './primordials.mjs';
import { getInitialIterations, maxTime, minTime, runBenchmark } from './lifecycle.mjs';
import { debugBench, timer } from './clock.mjs';
import { validateFunction, validateNumber, validateObject, validateString } from './validators.mjs';

class Benchmark {
#name;
Expand Down Expand Up @@ -36,7 +34,7 @@ class Benchmark {
}
}

class Suite {
export class Suite {
#benchmarks;
#reporter;

Expand Down Expand Up @@ -116,7 +114,3 @@ class Suite {
return results;
}
}

module.exports = {
Suite,
};
33 changes: 7 additions & 26 deletions lib/lifecycle.js → lib/lifecycle.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
'use strict';

const {
MathMin,
MathMax,
MathRound,
Number,
NumberMAX_SAFE_INTEGER,
} = primordials;
const { clockBenchmark, debugBench, timer, MIN_RESOLUTION } = require('internal/benchmark/clock');
const {
StatisticalHistogram,
kStatisticalHistogramRecord,
kStatisticalHistogramFinish,
} = require('internal/benchmark/histogram');
import { MathMax, MathMin, MathRound, NumberMAX_SAFE_INTEGER } from './primordials.mjs';
import { clockBenchmark, debugBench, MIN_RESOLUTION, timer } from './clock.mjs';
import { kStatisticalHistogramFinish, kStatisticalHistogramRecord, StatisticalHistogram } from './histogram.mjs';

// 0.05 - Arbitrary number used in some benchmark tools
const minTime = 0.05;
export const minTime = 0.05;

// 0.5s - Arbitrary number used in some benchmark tools
const maxTime = 0.5;
export const maxTime = 0.5;

/**
* @param {number} durationPerOp The amount of time each operation takes
Expand All @@ -30,7 +18,7 @@ function getItersForOpDuration(durationPerOp, targetTime) {
return MathMin(NumberMAX_SAFE_INTEGER, MathMax(1, MathRound(totalOpsForMinTime)));
}

async function getInitialIterations(bench) {
export async function getInitialIterations(bench) {
const { 0: duration, 1: realIterations } = await clockBenchmark(bench, 30);

// Just to avoid issues with empty fn
Expand All @@ -43,7 +31,7 @@ async function getInitialIterations(bench) {
return getItersForOpDuration(durationPerOp, bench.minTime);
}

async function runBenchmark(bench, initialIterations) {
export async function runBenchmark(bench, initialIterations) {
const histogram = new StatisticalHistogram();

let startClock;
Expand Down Expand Up @@ -86,10 +74,3 @@ async function runBenchmark(bench, initialIterations) {
histogram,
};
}

module.exports = {
minTime,
maxTime,
runBenchmark,
getInitialIterations,
};
18 changes: 18 additions & 0 deletions lib/primordials.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const createPrototypePrimordial = (prototypeFn) => (_this, ...args) => prototypeFn.apply(_this, args);

export const ArrayPrototypePush = createPrototypePrimordial(Array.prototype.push);
export const ArrayPrototypeSort = createPrototypePrimordial(Array.prototype.sort);
export const ArrayPrototypeReduce = createPrototypePrimordial(Array.prototype.reduce);
export const ArrayPrototypeFilter = createPrototypePrimordial(Array.prototype.filter);
export const ArrayIsArray = Array.isArray;
export const FunctionPrototypeBind = createPrototypePrimordial(Function.prototype.bind);
export const MathFloor = Math.floor;
export const MathMin = Math.min;
export const MathMax = Math.max;
export const MathCeil = Math.ceil;
export const MathPow = Math.pow;
export const MathSqrt = Math.sqrt;
export const MathRound = Math.round;
export const NumberMAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
export const NumberPrototypeToFixed = createPrototypePrimordial(Number.prototype.toFixed);
export const NumberIsNaN = Number.isNaN;
19 changes: 4 additions & 15 deletions lib/report.js → lib/report.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
'use strict';

const {
NumberPrototypeToFixed,
globalThis,
} = primordials;
const { Intl } = globalThis;

const { timer } = require('internal/benchmark/clock');
import { NumberPrototypeToFixed } from './primordials.mjs';
import { timer } from './clock.mjs';

const formatter = Intl.NumberFormat(undefined, {
notation: 'standard',
maximumFractionDigits: 2,
});

function reportConsoleBench(bench, result) {
export function reportConsoleBench(bench, result) {
const opsSecReported = result.opsSec < 100 ?
NumberPrototypeToFixed(result.opsSec, 2) :
NumberPrototypeToFixed(result.opsSec, 0);
Expand All @@ -24,7 +17,7 @@ function reportConsoleBench(bench, result) {
process.stdout.write(formatter.format(
NumberPrototypeToFixed(result.histogram.cv, 2)),
);
process.stdout.write(`% (${result.histogram.samples.length} runs sampled)\t`);
process.stdout.write(`% (${result.histogram.samples.length} runs sampled) `);

process.stdout.write('min..max=(');
process.stdout.write(timer.format(result.histogram.min));
Expand All @@ -36,7 +29,3 @@ function reportConsoleBench(bench, result) {
process.stdout.write(timer.format(result.histogram.percentile(99)));
process.stdout.write('\n');
}

module.exports = {
reportConsoleBench,
};
Loading

0 comments on commit c60c80e

Please sign in to comment.