-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
181 lines (181 loc) · 6.34 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*! micro-bmark - MIT License (c) 2020 Paul Miller, 2010-2016 Mathias Bynens, John-David Dalton, Robert Kieffer from JSLitmus.js */
const _c = String.fromCharCode(27);
const red = _c + '[31m';
const green = _c + '[32m';
const blue = _c + '[34m';
const reset = _c + '[0m';
function getTime() {
// @ts-ignore
return process.hrtime.bigint();
}
function logMem() {
const mapping = {
heapTotal: 'heap',
heapUsed: 'used',
external: 'ext',
arrayBuffers: 'arr',
};
// @ts-ignore
const vals = Object.entries(process.memoryUsage())
.filter((entry) => {
const [k, v] = entry;
return v > 100000 && k !== 'external';
})
.map((entry) => {
const [k, v] = entry;
return `${mapping[k] || k}=${`${(v / 1000000).toFixed(1)}mb`}`;
});
// @ts-ignore
console.log('RAM:', vals.join(' '));
}
// T-Distribution two-tailed critical values for 95% confidence.
// http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
// prettier-ignore
const tTable = {
'1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
'7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
'13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
'19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
'25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
'infinity': 1.96
};
const units = [
{ symbol: 'min', val: 60n * 10n ** 9n, threshold: 5n },
{ symbol: 's', val: 10n ** 9n, threshold: 10n },
{ symbol: 'ms', val: 10n ** 6n, threshold: 1n },
{ symbol: 'μs', val: 10n ** 3n, threshold: 1n },
{ symbol: 'ns', val: 0n, threshold: 1n },
];
const formatter = Intl.NumberFormat('en-US');
// duration formatter
function formatDuration(duration) {
for (let i = 0; i < units.length; i++) {
const { symbol, threshold, val } = units[i];
if (duration >= val * threshold) {
const div = val === 0n ? 1n : val;
return (duration / div).toString() + symbol;
}
}
throw new Error('Invalid duration ' + duration);
}
function calcSum(list, isBig = true) {
// @ts-ignore
return list.reduce((a, b) => a + b, (isBig ? 0n : 0));
}
function isFirstBig(list) {
return list.length > 0 && typeof list[0] === 'bigint';
}
function calcMean(list) {
const len = list.length;
const isBig = isFirstBig(list);
const tlen = isBig ? BigInt(len) : len;
// @ts-ignore
return calcSum(list, isBig) / tlen;
}
function calcDeviation(list) {
const isBig = isFirstBig(list);
const mean = calcMean(list);
const square = isBig ? (a) => a ** 2n : (a) => a ** 2;
// @ts-ignore
const diffs = list.map((val) => square(val - mean));
const variance = Number(calcSum(diffs, isBig)) / list.length - 1;
return Math.sqrt(variance);
}
function calcCorrelation(x, y) {
const isBig = isFirstBig(x);
const checker = isBig ? (a) => typeof a === 'bigint' : (a) => typeof a === 'number';
const err = `expected array of ${isBig ? 'bigints' : 'numbers'}`;
if (!x.every(checker))
throw new Error('x: ' + err);
if (!y.every(checker))
throw new Error('y: ' + err);
const meanX = calcMean(x);
const meanY = calcMean(y);
const sum = calcSum(x.map((val, i) => (val - meanX) * (y[i] - meanY)), isBig);
const observation = Number(sum) / (calcDeviation(x) * calcDeviation(y));
return observation / (x.length - 1);
}
// Mutates array by sorting it
function calcStats(list) {
list.sort((a, b) => Number(a - b));
const samples = list.length;
const mean = calcMean(list);
const median = list[Math.floor(samples / 2)];
const min = list[0];
const max = list[samples - 1];
// Compute the standard error of the mean
// a.k.a. the standard deviation of the sampling distribution of the sample mean
const sem = calcDeviation(list) / Math.sqrt(samples);
const df = samples - 1; // degrees of freedom
// @ts-ignore
const critical = tTable[Math.round(df) || 1] || tTable.infinity; // critical value
const moe = sem * critical; // margin of error
const rme = (moe / Number(mean)) * 100 || 0; // relative margin of error
const formatted = `${red}± ${rme.toFixed(2)}% (${formatDuration(min)}..${formatDuration(max)})${reset}`;
return { rme, min, max, mean, median, formatted };
}
async function benchmarkRaw(samples, callback) {
if (!Number.isSafeInteger(samples) || samples <= 0)
throw new Error('samples must be a number');
if (typeof callback !== 'function')
throw new Error('callback must be a function');
// List containing sample times
const list = new Array(samples);
for (let i = 0; i < samples; i++) {
const start = getTime();
const val = callback(i);
if (val instanceof Promise)
await val;
const stop = getTime();
list[i] = stop - start;
}
const stats = calcStats(list);
const perItemStr = formatDuration(stats.mean);
const sec = units[1].val;
const perSec = sec / stats.mean;
const perSecStr = formatter.format(sec / stats.mean);
return { stats, perSecStr, perSec, perItemStr, measurements: list };
}
export async function mark(label, samplesFN, callbackFN) {
if (typeof label !== 'string')
throw new Error('label must be a string');
let samples;
let callback;
if (typeof samplesFN === 'function') {
callback = samplesFN;
samples = 1;
}
else {
if (samplesFN == null)
samplesFN = 1;
if (typeof callbackFN !== 'function')
throw new Error('callback must be a function');
samples = samplesFN;
callback = callbackFN;
}
const { stats, perSecStr, perItemStr, measurements } = await benchmarkRaw(samples, callback);
let str = `${label} `;
if (samples === 1) {
str += perItemStr;
}
else {
str += `x ${green}${perSecStr}${reset} ops/sec @ ${blue}${perItemStr}${reset}/op`;
}
if (stats.rme >= 1)
str += ` ${stats.formatted}`;
// @ts-ignore
console.log(str);
// Destroy the list, simplify the life for garbage collector
measurements.length = 0;
return;
}
export default mark;
export const utils = {
getTime,
logMem,
formatDuration,
calcStats,
calcDeviation,
calcCorrelation,
benchmarkRaw,
};