Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

81
benchmark/README.md Normal file
View File

@ -0,0 +1,81 @@
# Node.js Core Benchmarks
This folder contains code and data used to measure performance
of different Node.js implementations and different ways of
writing JavaScript run by the built-in JavaScript engine.
For a detailed guide on how to write and run benchmarks in this
directory, see [the guide on benchmarks](../doc/contributing/writing-and-running-benchmarks.md).
## Table of Contents
* [File tree structure](#file-tree-structure)
* [Common API](#common-api)
## File tree structure
### Directories
Benchmarks testing the performance of a single node submodule are placed into a
directory with the corresponding name, so that they can be executed by submodule
or individually.
Benchmarks that span multiple submodules may either be placed into the `misc`
directory or into a directory named after the feature they benchmark.
E.g. benchmarks for various new ECMAScript features and their pre-ES2015
counterparts are placed in a directory named `es`.
Fixtures that are not specific to a certain benchmark but can be reused
throughout the benchmark suite should be placed in the `fixtures` directory.
### Other Top-level files
The top-level files include common dependencies of the benchmarks
and the tools for launching benchmarks and visualizing their output.
The actual benchmark scripts should be placed in their corresponding
directories.
* `_benchmark_progress.js`: implements the progress bar displayed
when running `compare.js`
* `_cli.js`: parses the command line arguments passed to `compare.js`,
`run.js` and `scatter.js`
* `_cli.R`: parses the command line arguments passed to `compare.R`
* `_http-benchmarkers.js`: selects and runs external tools for benchmarking
the `http` subsystem.
* `bar.R`: R script for visualizing the output of benchmarks with bar plots.
* `common.js`: see [Common API](#common-api).
* `compare.js`: command line tool for comparing performance between different
Node.js binaries.
* `compare.R`: R script for statistically analyzing the output of
`compare.js`
* `run.js`: command line tool for running individual benchmark suite(s).
* `scatter.js`: command line tool for comparing the performance
between different parameters in benchmark configurations,
for example to analyze the time complexity.
* `scatter.R`: R script for visualizing the output of `scatter.js` with
scatter plots.
## Common API
The common.js module is used by benchmarks for consistency across repeated
tasks. It has a number of helpful functions and properties to help with
writing benchmarks.
### `createBenchmark(fn, configs[, options])`
See [the guide on writing benchmarks](../doc/contributing/writing-and-running-benchmarks.md#basics-of-a-benchmark).
### `default_http_benchmarker`
The default benchmarker used to run HTTP benchmarks.
See [the guide on writing HTTP benchmarks](../doc/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark).
### `PORT`
The default port used to run HTTP benchmarks.
See [the guide on writing HTTP benchmarks](../doc/contributing/writing-and-running-benchmarks.md#creating-an-http-benchmark).
### `sendResult(data)`
Used in special benchmarks that can't use `createBenchmark` and the object
it returns to accomplish what they need. This function reports timing
data to the parent process (usually created by running `compare.js`, `run.js` or
`scatter.js`).

View File

@ -0,0 +1,119 @@
'use strict';
const readline = require('readline');
function pad(input, minLength, fill) {
const result = String(input);
const padding = fill.repeat(Math.max(0, minLength - result.length));
return `${padding}${result}`;
}
function fraction(numerator, denominator) {
const fdenominator = String(denominator);
const fnumerator = pad(numerator, fdenominator.length, ' ');
return `${fnumerator}/${fdenominator}`;
}
function getTime(diff) {
const time = Math.ceil(diff[0] + diff[1] / 1e9);
const hours = pad(Math.floor(time / 3600), 2, '0');
const minutes = pad(Math.floor((time % 3600) / 60), 2, '0');
const seconds = pad((time % 3600) % 60, 2, '0');
return `${hours}:${minutes}:${seconds}`;
}
// A run is an item in the job queue: { binary, filename, iter }
// A config is an item in the subqueue: { binary, filename, iter, configs }
class BenchmarkProgress {
constructor(queue, benchmarks) {
this.queue = queue; // Scheduled runs.
this.benchmarks = benchmarks; // Filenames of scheduled benchmarks.
this.completedRuns = 0; // Number of completed runs.
this.scheduledRuns = queue.length; // Number of scheduled runs.
// Time when starting to run benchmarks.
this.startTime = process.hrtime();
// Number of times each file will be run (roughly).
this.runsPerFile = queue.length / benchmarks.length;
this.currentFile = ''; // Filename of current benchmark.
// Number of configurations already run for the current file.
this.completedConfig = 0;
// Total number of configurations for the current file
this.scheduledConfig = 0;
}
startQueue(index) {
this.kStartOfQueue = index;
this.currentFile = this.queue[index].filename;
this.interval = setInterval(() => {
if (this.completedRuns === this.scheduledRuns) {
clearInterval(this.interval);
} else {
this.updateProgress();
}
}, 1000);
}
startSubqueue(data, index) {
// This subqueue is generated by a new benchmark
if (data.name !== this.currentFile || index === this.kStartOfQueue) {
this.currentFile = data.name;
this.scheduledConfig = data.queueLength;
}
this.completedConfig = 0;
this.updateProgress();
}
completeConfig() {
this.completedConfig++;
this.updateProgress();
}
completeRun() {
this.completedRuns++;
this.updateProgress();
}
getProgress() {
// Get time as soon as possible.
const diff = process.hrtime(this.startTime);
const completedRuns = this.completedRuns;
const scheduledRuns = this.scheduledRuns;
const finished = completedRuns === scheduledRuns;
// Calculate numbers for fractions.
const runsPerFile = this.runsPerFile;
const completedFiles = Math.floor(completedRuns / runsPerFile);
const scheduledFiles = this.benchmarks.length;
const completedRunsForFile =
finished ? runsPerFile : completedRuns % runsPerFile;
const completedConfig = this.completedConfig;
const scheduledConfig = this.scheduledConfig;
// Calculate the percentage.
let runRate = 0; // Rate of current incomplete run.
if (completedConfig !== scheduledConfig) {
runRate = completedConfig / scheduledConfig;
}
const completedRate = ((completedRuns + runRate) / scheduledRuns);
const percent = pad(Math.floor(completedRate * 100), 3, ' ');
const caption = finished ? 'Done\n' : this.currentFile;
return `[${getTime(diff)}|% ${percent}| ` +
`${fraction(completedFiles, scheduledFiles)} files | ` +
`${fraction(completedRunsForFile, runsPerFile)} runs | ` +
`${fraction(completedConfig, scheduledConfig)} configs]: ` +
`${caption} `;
}
updateProgress() {
if (!process.stderr.isTTY || process.stdout.isTTY) {
return;
}
readline.clearLine(process.stderr);
readline.cursorTo(process.stderr, 0);
process.stderr.write(this.getProgress());
}
}
module.exports = BenchmarkProgress;

24
benchmark/_cli.R Normal file
View File

@ -0,0 +1,24 @@
args = commandArgs(TRUE);
args.options = list();
temp.option.key = NULL;
for (arg in args) {
# Optional arguments declaration
if (substring(arg, 1, 1) == '-') {
temp.option.key = substring(arg, 2);
if (substring(arg, 2, 2) == '-') {
temp.option.key = substring(arg, 3);
}
args.options[[temp.option.key]] = TRUE;
}
# Optional arguments value
else if (!is.null(temp.option.key)) {
args.options[[temp.option.key]] = arg;
temp.option.key = NULL;
}
}

149
benchmark/_cli.js Normal file
View File

@ -0,0 +1,149 @@
'use strict';
const fs = require('fs');
const path = require('path');
// Create an object of all benchmark scripts
const benchmarks = {};
fs.readdirSync(__dirname)
.filter((name) => {
return name !== 'fixtures' &&
fs.statSync(path.resolve(__dirname, name)).isDirectory();
})
.forEach((category) => {
benchmarks[category] = fs.readdirSync(path.resolve(__dirname, category))
.filter((filename) => filename[0] !== '.' && filename[0] !== '_');
});
function CLI(usage, settings) {
if (process.argv.length < 3) {
this.abort(usage); // Abort will exit the process
}
this.usage = usage;
this.optional = {};
this.items = [];
this.test = false;
for (const argName of settings.arrayArgs) {
this.optional[argName] = [];
}
let currentOptional = null;
let mode = 'both'; // Possible states are: [both, option, item]
for (const arg of process.argv.slice(2)) {
if (arg === '--help') this.abort(usage);
if (arg === '--') {
// Only items can follow --
mode = 'item';
} else if (mode === 'both' && arg[0] === '-') {
// Optional arguments declaration
if (arg[1] === '-') {
currentOptional = arg.slice(2);
} else {
currentOptional = arg.slice(1);
}
if (settings.boolArgs && settings.boolArgs.includes(currentOptional)) {
this.optional[currentOptional] = true;
mode = 'both';
} else {
// Expect the next value to be option related (either -- or the value)
mode = 'option';
}
} else if (mode === 'option') {
// Optional arguments value
if (settings.arrayArgs.includes(currentOptional)) {
this.optional[currentOptional].push(arg);
} else {
this.optional[currentOptional] = arg;
}
// The next value can be either an option or an item
mode = 'both';
} else if (arg === 'test') {
this.test = true;
} else if (['both', 'item'].includes(mode)) {
// item arguments
this.items.push(arg);
// The next value must be an item
mode = 'item';
} else {
// Bad case, abort
this.abort(usage);
}
}
}
module.exports = CLI;
CLI.prototype.abort = function(msg) {
console.error(msg);
process.exit(1);
};
CLI.prototype.benchmarks = function() {
const paths = [];
if (this.items.includes('all')) {
this.items = Object.keys(benchmarks);
}
for (const category of this.items) {
if (benchmarks[category] === undefined) {
console.error(`The "${category}" category does not exist.`);
process.exit(1);
}
for (const scripts of benchmarks[category]) {
if (this.shouldSkip(scripts)) continue;
paths.push(path.join(category, scripts));
}
}
return paths;
};
CLI.prototype.shouldSkip = function(scripts) {
const filters = this.optional.filter || [];
const excludes = this.optional.exclude || [];
let skip = filters.length > 0;
for (const filter of filters) {
if (scripts.lastIndexOf(filter) !== -1) {
skip = false;
}
}
for (const exclude of excludes) {
if (scripts.lastIndexOf(exclude) !== -1) {
skip = true;
}
}
return skip;
};
/**
* Extracts the CPU core setting from the CLI arguments.
* @returns {string|null} The CPU core setting if found, otherwise null.
*/
CLI.prototype.getCpuCoreSetting = function() {
const cpuCoreSetting = this.optional.set.find((s) => s.startsWith('CPUSET='));
if (!cpuCoreSetting) return null;
const value = cpuCoreSetting.split('=')[1];
// Validate the CPUSET value to match patterns like "0", "0-2", "0,1,2", "0,2-4,6" or "0,0,1-2"
const isValid = /^(\d+(-\d+)?)(,\d+(-\d+)?)*$/.test(value);
if (!isValid) {
throw new Error(`
Invalid CPUSET format: "${value}". Please use a single core number (e.g., "0"),
a range of cores (e.g., "0-3"), or a list of cores/ranges
(e.g., "0,2,4" or "0-2,4").\n\n${this.usage}
`);
}
return value;
};

View File

@ -0,0 +1,266 @@
'use strict';
const child_process = require('child_process');
const path = require('path');
const fs = require('fs');
const requirementsURL =
'https://github.com/nodejs/node/blob/HEAD/doc/contributing/writing-and-running-benchmarks.md#http-benchmark-requirements';
// The port used by servers and wrk
exports.PORT = Number(process.env.PORT) || 12346;
class AutocannonBenchmarker {
constructor() {
const shell = (process.platform === 'win32');
this.name = 'autocannon';
this.opts = { shell };
this.executable = shell ? 'autocannon.cmd' : 'autocannon';
const result = child_process.spawnSync(this.executable, ['-h'], this.opts);
if (shell) {
this.present = (result.status === 0);
} else {
this.present = !(result.error && result.error.code === 'ENOENT');
}
}
create(options) {
const args = [
'-d', options.duration,
'-c', options.connections,
'-j',
'-n',
];
for (const field in options.headers) {
if (this.opts.shell) {
args.push('-H', `'${field}=${options.headers[field]}'`);
} else {
args.push('-H', `${field}=${options.headers[field]}`);
}
}
const scheme = options.scheme || 'http';
args.push(`${scheme}://127.0.0.1:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args, this.opts);
return child;
}
processResults(output) {
let result;
try {
result = JSON.parse(output);
} catch {
return undefined;
}
if (!result || !result.requests || !result.requests.average) {
return undefined;
}
return result.requests.average;
}
}
class WrkBenchmarker {
constructor() {
this.name = 'wrk';
this.executable = 'wrk';
const result = child_process.spawnSync(this.executable, ['-h']);
this.present = !(result.error && result.error.code === 'ENOENT');
}
create(options) {
const duration = typeof options.duration === 'number' ?
Math.max(options.duration, 1) :
options.duration;
const scheme = options.scheme || 'http';
const args = [
'-d', duration,
'-c', options.connections,
'-t', Math.min(options.connections, require('os').availableParallelism() || 8),
`${scheme}://127.0.0.1:${options.port}${options.path}`,
];
for (const field in options.headers) {
args.push('-H', `${field}: ${options.headers[field]}`);
}
const child = child_process.spawn(this.executable, args);
return child;
}
processResults(output) {
const throughputRe = /Requests\/sec:[ \t]+([0-9.]+)/;
const match = output.match(throughputRe);
const throughput = match && +match[1];
if (!isFinite(throughput)) {
return undefined;
}
return throughput;
}
}
/**
* Simple, single-threaded benchmarker for testing if the benchmark
* works
*/
class TestDoubleBenchmarker {
constructor(type) {
// `type` is the type of benchmarker. Possible values are 'http', 'https',
// and 'http2'.
this.name = `test-double-${type}`;
this.executable = path.resolve(__dirname, '_test-double-benchmarker.js');
this.present = fs.existsSync(this.executable);
this.type = type;
}
create(options) {
process.env.duration ||= options.duration || 5;
const scheme = options.scheme || 'http';
const env = {
test_url: `${scheme}://127.0.0.1:${options.port}${options.path}`,
...process.env,
};
const child = child_process.fork(this.executable,
[this.type],
{ silent: true, env });
return child;
}
processResults(output) {
let result;
try {
result = JSON.parse(output);
} catch {
return undefined;
}
return result.throughput;
}
}
/**
* HTTP/2 Benchmarker
*/
class H2LoadBenchmarker {
constructor() {
this.name = 'h2load';
this.executable = 'h2load';
const result = child_process.spawnSync(this.executable, ['-h']);
this.present = !(result.error && result.error.code === 'ENOENT');
}
create(options) {
const args = [];
if (typeof options.requests === 'number')
args.push('-n', options.requests);
if (typeof options.clients === 'number')
args.push('-c', options.clients);
if (typeof options.threads === 'number')
args.push('-t', options.threads);
if (typeof options.maxConcurrentStreams === 'number')
args.push('-m', options.maxConcurrentStreams);
if (typeof options.initialWindowSize === 'number')
args.push('-w', options.initialWindowSize);
if (typeof options.sessionInitialWindowSize === 'number')
args.push('-W', options.sessionInitialWindowSize);
if (typeof options.rate === 'number')
args.push('-r', options.rate);
if (typeof options.ratePeriod === 'number')
args.push(`--rate-period=${options.ratePeriod}`);
if (typeof options.duration === 'number')
args.push('-T', options.duration);
if (typeof options.timeout === 'number')
args.push('-N', options.timeout);
if (typeof options.headerTableSize === 'number')
args.push(`--header-table-size=${options.headerTableSize}`);
if (typeof options.encoderHeaderTableSize === 'number') {
args.push(
`--encoder-header-table-size=${options.encoderHeaderTableSize}`);
}
const scheme = options.scheme || 'http';
const host = options.host || '127.0.0.1';
args.push(`${scheme}://${host}:${options.port}${options.path}`);
const child = child_process.spawn(this.executable, args);
return child;
}
processResults(output) {
const rex = /(\d+\.\d+) req\/s/;
return rex.exec(output)[1];
}
}
const http_benchmarkers = [
new WrkBenchmarker(),
new AutocannonBenchmarker(),
new TestDoubleBenchmarker('http'),
new TestDoubleBenchmarker('https'),
new TestDoubleBenchmarker('http2'),
new H2LoadBenchmarker(),
];
const benchmarkers = {};
http_benchmarkers.forEach((benchmarker) => {
benchmarkers[benchmarker.name] = benchmarker;
if (!exports.default_http_benchmarker && benchmarker.present) {
exports.default_http_benchmarker = benchmarker.name;
}
});
exports.run = function(options, callback) {
options = {
port: exports.PORT,
path: '/',
connections: 100,
duration: 5,
benchmarker: exports.default_http_benchmarker,
...options,
};
if (!options.benchmarker) {
callback(new Error('Could not locate required http benchmarker. See ' +
`${requirementsURL} for further instructions.`));
return;
}
const benchmarker = benchmarkers[options.benchmarker];
if (!benchmarker) {
callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
'is not supported'));
return;
}
if (!benchmarker.present) {
callback(new Error(`Requested benchmarker '${options.benchmarker}' ` +
'is not installed'));
return;
}
const benchmarker_start = process.hrtime.bigint();
const child = benchmarker.create(options);
child.stderr.pipe(process.stderr);
let stdout = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => stdout += chunk);
child.once('close', (code) => {
const benchmark_end = process.hrtime.bigint();
if (code) {
let error_message = `${options.benchmarker} failed with ${code}.`;
if (stdout !== '') {
error_message += ` Output: ${stdout}`;
}
callback(new Error(error_message), code);
return;
}
const result = benchmarker.processResults(stdout);
if (result === undefined) {
callback(new Error(
`${options.benchmarker} produced strange output: ${stdout}`), code);
return;
}
const elapsed = benchmark_end - benchmarker_start;
callback(null, code, options.benchmarker, result, elapsed);
});
};

View File

@ -0,0 +1,54 @@
'use strict';
const myModule = process.argv[2];
if (!['http', 'https', 'http2'].includes(myModule)) {
throw new Error(`Invalid module for benchmark test double: ${myModule}`);
}
let options;
if (myModule === 'https') {
options = { rejectUnauthorized: false };
}
const http = require(myModule);
const duration = +process.env.duration;
const url = process.env.test_url;
const start = process.hrtime();
let throughput = 0;
function request(res, client) {
res.resume();
res.on('error', () => {});
res.on('end', () => {
throughput++;
const [sec, nanosec] = process.hrtime(start);
const ms = sec * 1000 + nanosec / 1e6;
if (ms < duration * 1000) {
run();
} else {
console.log(JSON.stringify({ throughput }));
if (client) {
client.destroy();
process.exit(0);
}
}
});
}
function run() {
if (http.get) { // HTTP or HTTPS
if (options) {
http.get(url, options, request);
} else {
http.get(url, request);
}
} else { // HTTP/2
const client = http.connect(url);
client.on('error', () => {});
request(client.request(), client);
}
}
run();

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [5e6],
kind: ['default-reason', 'same-reason'],
});
function main({ n, kind }) {
switch (kind) {
case 'default-reason':
bench.start();
for (let i = 0; i < n; ++i)
AbortSignal.abort();
bench.end(n);
break;
case 'same-reason': {
const reason = new Error('same reason');
bench.start();
for (let i = 0; i < n; ++i)
AbortSignal.abort(reason);
bench.end(n);
break;
}
default:
throw new Error('Invalid kind');
}
}

View File

@ -0,0 +1,37 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [200],
size: [2, 75],
});
const baseObject = {
a: 1,
b: {
c: 2,
d: [3, 4, 5],
e: 'fghi',
j: {
k: 6,
},
},
};
function createObjects(size) {
return Array.from({ length: size }, () => baseObject);
}
function main({ n, size }) {
bench.start();
for (let i = 0; i < n; ++i) {
new assert.AssertionError({
actual: {},
expected: createObjects(size),
operator: 'partialDeepStrictEqual',
stackStartFunction: () => {},
});
}
bench.end(n);
}

View File

@ -0,0 +1,53 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e4],
len: [1e2, 1e3],
strict: [0, 1],
arrayBuffer: [0, 1],
method: ['deepEqual', 'notDeepEqual', 'unequal_length', 'partial'],
}, {
combinationFilter: (p) => {
return p.strict === 1 || p.method === 'deepEqual';
},
});
function main({ len, n, method, strict, arrayBuffer }) {
let actual = Buffer.alloc(len);
let expected = Buffer.alloc(len + Number(method === 'unequal_length'));
if (method === 'unequal_length') {
method = 'notDeepEqual';
}
if (method === 'partial') {
method = 'partialDeepStrictEqual';
} else if (strict) {
method = method.replace('eep', 'eepStrict');
}
for (let i = 0; i < len; i++) {
actual.writeInt8(i % 128, i);
expected.writeInt8(i % 128, i);
}
if (method.includes('not')) {
const position = Math.floor(len / 2);
expected[position] = expected[position] + 1;
}
const fn = assert[method];
if (arrayBuffer) {
actual = actual.buffer;
expected = expected.buffer;
}
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,80 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [2e3],
len: [5e2],
strict: [0, 1],
method: [
'deepEqual_primitiveOnly',
'deepEqual_objectOnly',
'deepEqual_mixed',
'notDeepEqual_primitiveOnly',
'notDeepEqual_objectOnly',
'notDeepEqual_mixed',
],
});
function benchmark(method, n, values, values2) {
const actual = new Map(values);
// Prevent reference equal elements
const deepCopy = JSON.parse(JSON.stringify(values2 ? values2 : values));
const expected = new Map(deepCopy);
bench.start();
for (let i = 0; i < n; ++i) {
method(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict }) {
const array = Array.from({ length: len }, () => '');
switch (method) {
case 'deepEqual_primitiveOnly': {
const values = array.map((_, i) => [`str_${i}`, 123]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'deepEqual_objectOnly': {
const values = array.map((_, i) => [[`str_${i}`, 1], 123]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'deepEqual_mixed': {
const values = array.map(
(_, i) => [i % 2 ? [`str_${i}`, 1] : `str_${i}`, 123],
);
benchmark(strict ? deepStrictEqual : deepEqual, n, values);
break;
}
case 'notDeepEqual_primitiveOnly': {
const values = array.map((_, i) => [`str_${i}`, 123]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = ['w00t', 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
case 'notDeepEqual_objectOnly': {
const values = array.map((_, i) => [[`str_${i}`, 1], 123]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = [['w00t'], 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
case 'notDeepEqual_mixed': {
const values = array.map(
(_, i) => [i % 2 ? [`str_${i}`, 1] : `str_${i}`, 123],
);
const values2 = values.slice(0);
values2[0] = ['w00t', 123];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2);
break;
}
default:
throw new Error(`Unsupported method ${method}`);
}
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50, 2e2],
size: [1e2, 1e4],
method: ['deepEqual', 'notDeepEqual', 'deepStrictEqual', 'notDeepStrictEqual'],
}, {
combinationFilter: (p) => {
return p.size === 1e4 && p.n === 50 ||
p.size === 1e3 && p.n === 2e2 ||
p.size === 1e2 && p.n === 2e3 ||
p.size === 1;
},
});
function createObj(size, add = '') {
return Array.from({ length: size }, (n) => ({
foo: 'yarp',
nope: {
bar: `123${add}`,
a: [1, 2, 3],
baz: n,
c: {},
b: [],
},
}));
}
function main({ size, n, method }) {
const fn = assert[method];
const actual = createObj(size);
const expected = method.includes('not') ? createObj(size, '4') : createObj(size);
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,80 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const circular = {};
circular.circular = circular;
const circular2 = {};
circular2.circular = circular2;
const notCircular = {};
notCircular.circular = {};
const primValues = {
'null_prototype': { __proto__: null },
'string': 'abcdef',
'number': 1_000,
'boolean': true,
'object': { property: 'abcdef' },
'array': [1, 2, 3],
'set_object': new Set([[1]]),
'set_simple': new Set([1, 2, 3]),
'circular': circular,
'empty_object': {},
'regexp': /abc/i,
'date': new Date(),
};
const primValues2 = {
'null_prototype': { __proto__: null },
'object': { property: 'abcdef' },
'array': [1, 2, 3],
'set_object': new Set([[1]]),
'set_simple': new Set([1, 3, 2]),
'circular': circular2,
'empty_object': {},
'regexp': /abc/i,
'date': new Date(primValues.date),
};
const primValuesUnequal = {
'null_prototype': { __proto__: { __proto__: null } },
'string': 'abcdez',
'number': 1_001,
'boolean': false,
'object': { property2: 'abcdef' },
'array': [1, 3, 2],
'set_object': new Set([[2]]),
'set_simple': new Set([1, 4, 2]),
'circular': notCircular,
'empty_object': [],
'regexp': /abc/g,
'date': new Date(primValues.date.getTime() + 1),
};
const bench = common.createBenchmark(main, {
primitive: Object.keys(primValues),
n: [1e5],
strict: [0, 1],
method: ['deepEqual', 'notDeepEqual'],
}, {
combinationFilter: (p) => {
return p.strict === 1 || p.method === 'deepEqual';
},
});
function main({ n, primitive, method, strict }) {
const prim = primValues[primitive];
const actual = primValues2[primitive] ?? prim;
const expected = method.includes('not') ? primValuesUnequal[primitive] : prim;
if (strict) {
method = method.replace('eep', 'eepStrict');
}
const fn = assert[method];
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,99 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [1e3],
len: [2, 1e2],
strict: [0, 1],
order: ['insert', 'random', 'reversed'],
method: [
'deepEqual_primitiveOnly',
'deepEqual_objectOnly',
'deepEqual_mixed',
'notDeepEqual_primitiveOnly',
'notDeepEqual_objectOnly',
'notDeepEqual_mixed',
],
}, {
combinationFilter(p) {
return p.order !== 'random' || p.strict === 1 && p.method !== 'notDeepEqual_objectOnly';
},
});
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
function benchmark(method, n, values, values2, order) {
const actual = new Set(values);
// Prevent reference equal elements
let deepCopy = JSON.parse(JSON.stringify(values2));
if (order === 'reversed') {
deepCopy = deepCopy.reverse();
} else if (order === 'random') {
shuffleArray(deepCopy);
}
const expected = new Set(deepCopy);
bench.start();
for (let i = 0; i < n; ++i) {
method(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict, order }) {
const array = Array.from({ length: len }, () => '');
switch (method) {
case 'deepEqual_primitiveOnly': {
const values = array.map((_, i) => `str_${i}`);
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'deepEqual_objectOnly': {
const values = array.map((_, i) => [`str_${i}`, null]);
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'deepEqual_mixed': {
const values = array.map((_, i) => {
return i % 2 ? [`str_${i}`, null] : `str_${i}`;
});
benchmark(strict ? deepStrictEqual : deepEqual, n, values, values, order);
break;
}
case 'notDeepEqual_primitiveOnly': {
const values = array.map((_, i) => `str_${i}`);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = 'w00t';
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
case 'notDeepEqual_objectOnly': {
const values = array.map((_, i) => [`str_${i}`, null]);
const values2 = values.slice(0);
values2[Math.floor(len / 2)] = ['w00t'];
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
case 'notDeepEqual_mixed': {
const values = array.map((_, i) => {
return i % 2 ? [`str_${i}`, null] : `str_${i}`;
});
const values2 = values.slice();
values2[0] = 'w00t';
benchmark(strict ? notDeepStrictEqual : notDeepEqual, n, values, values2, order);
break;
}
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const { deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual } =
require('assert');
const bench = common.createBenchmark(main, {
n: [1e3],
len: [1e4],
strict: [1],
method: [
'deepEqual_Array',
'notDeepEqual_Array',
'deepEqual_sparseArray',
'notDeepEqual_sparseArray',
'deepEqual_Set',
'notDeepEqual_Set',
],
});
function run(fn, n, actual, expected) {
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}
function main({ n, len, method, strict }) {
let actual = Array.from({ length: len }, (_, i) => i);
// Contain one undefined value to trigger a specific code path
actual[0] = undefined;
let expected = actual.slice(0);
if (method.includes('not')) {
expected[len - 1] += 1;
}
switch (method) {
case 'deepEqual_sparseArray':
case 'notDeepEqual_sparseArray':
actual = new Array(len);
for (let i = 0; i < len; i += 2) {
actual[i] = i;
}
expected = actual.slice(0);
if (method.includes('not')) {
expected[len - 2] += 1;
run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected);
} else {
run(strict ? deepStrictEqual : deepEqual, n, actual, expected);
}
break;
case 'deepEqual_Array':
run(strict ? deepStrictEqual : deepEqual, n, actual, expected);
break;
case 'notDeepEqual_Array':
run(strict ? notDeepStrictEqual : notDeepEqual, n, actual, expected);
break;
case 'deepEqual_Set':
run(strict ? deepStrictEqual : deepEqual,
n, new Set(actual), new Set(expected));
break;
case 'notDeepEqual_Set':
run(strict ? notDeepStrictEqual : notDeepEqual,
n, new Set(actual), new Set(expected));
break;
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,51 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
type: [
'Int8Array',
'Uint8Array',
'Float32Array',
'Uint32Array',
],
n: [25000],
strict: [0, 1],
method: [
'deepEqual',
'notDeepEqual',
],
len: [1e2, 5e3],
}, {
combinationFilter(p) {
return p.strict === 1 ||
p.type !== 'Float32Array' ||
p.len === 1e2;
},
});
function main({ type, n, len, method, strict }) {
const clazz = global[type];
const actual = new clazz(len);
const expected = new clazz(len);
if (strict) {
method = method.replace('eep', 'eepStrict');
}
const fn = assert[method];
if (method.includes('not')) {
expected[Math.floor(len / 2)] = 123;
}
bench.start();
for (let i = 0; i < n; ++i) {
actual[0] = i;
expected[0] = i;
const pos = Math.ceil(len / 2) + 1;
actual[pos] = i;
expected[pos] = i;
fn(actual, expected);
}
bench.end(n);
}

28
benchmark/assert/match.js Normal file
View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e7],
method: ['match', 'doesNotMatch'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// might insight, due to only being a small wrapper around a native regexp
// call.
return p.n === 1;
},
});
function main({ n, method }) {
const fn = assert[method];
const actual = 'Example of string that will match';
const expected = method === 'match' ? /will match/ : /will not match/;
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,170 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [125],
size: [500],
extraProps: [0, 1],
datasetName: [
'objects',
'sets',
'setsWithObjects',
'maps',
'circularRefs',
'typedArrays',
'arrayBuffers',
'dataViewArrayBuffers',
'array',
],
});
function createArray(length, extraProps) {
if (extraProps) {
return Array.from({ length: length * 4 }, (_, i) => i);
}
return Array.from({ length }, (_, i) => i * 4);
}
function createObjects(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => ({
foo: 'yarp',
nope: {
bar: '123',
...(extraProps ? { a: [1, 2, i] } : {}),
c: {},
b: !depth ? createObjects(2, extraProps, depth + 1) : [],
},
}));
}
function createSetsWithObjects(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Set([
...(extraProps ? [{}] : []),
{
simple: 'object',
number: i,
},
['array', 'with', 'values'],
new Set([[], {}, { nested: i }]),
]));
}
function createSets(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Set([
'yarp',
...(extraProps ? ['123', 1, 2] : []),
i + 3,
null,
{
simple: 'object',
number: i,
},
['array', 'with', 'values'],
!depth ? new Set([1, { nested: i }]) : new Set(),
!depth ? createSets(2, extraProps, depth + 1) : null,
]));
}
function createMaps(length, extraProps, depth = 0) {
return Array.from({ length }, (_, i) => new Map([
...(extraProps ? [['primitiveKey', 'primitiveValue']] : []),
[42, 'numberKey'],
['objectValue', { a: 1, b: i }],
['arrayValue', [1, 2, i]],
['nestedMap', new Map([['a', i], ['b', { deep: true }]])],
[{ objectKey: true }, 'value from object key'],
[[1, i, 3], 'value from array key'],
[!depth ? createMaps(2, extraProps, depth + 1) : null, 'recursive value' + i],
]));
}
function createCircularRefs(length, extraProps) {
return Array.from({ length }, (_, i) => {
const circularSet = new Set();
const circularMap = new Map();
const circularObj = { name: 'circular object' };
circularSet.add('some value' + i);
circularSet.add(circularSet);
circularMap.set('self', circularMap);
circularMap.set('value', 'regular value');
circularObj.self = circularObj;
const objA = { name: 'A' };
const objB = { name: 'B' };
objA.ref = objB;
objB.ref = objA;
circularSet.add(objA);
circularMap.set('objB', objB);
return {
circularSet,
circularMap,
...extraProps ? { extra: i } : {},
circularObj,
objA,
objB,
};
});
}
function createTypedArrays(length, extraParts) {
const extra = extraParts ? [9, 8, 7] : [];
return Array.from({ length }, (_, i) => {
return {
uint8: new Uint8Array(new ArrayBuffer(32), 4, 4),
int16: new Int16Array([1, 2, ...extra, 3]),
uint32: new Uint32Array([i + 1, i + 2, ...extra, i + 3]),
float64: new Float64Array([1.1, 2.2, ...extra, i + 3.3]),
bigUint64: new BigUint64Array([1n, 2n, 3n]),
};
});
}
function createArrayBuffers(length, extra) {
return Array.from({ length }, (_, n) => {
const buffer = Buffer.alloc(n + (extra ? 1 : 0));
for (let i = 0; i < n; i++) {
buffer.writeInt8(i % 128, i);
}
return buffer.buffer;
});
}
function createDataViewArrayBuffers(length, extra) {
return createArrayBuffers(length, extra).map((buffer) => new DataView(buffer));
}
const datasetMappings = {
objects: createObjects,
sets: createSets,
setsWithObjects: createSetsWithObjects,
maps: createMaps,
circularRefs: createCircularRefs,
typedArrays: createTypedArrays,
arrayBuffers: createArrayBuffers,
dataViewArrayBuffers: createDataViewArrayBuffers,
array: createArray,
};
function getDatasets(datasetName, size, extra) {
return {
actual: datasetMappings[datasetName](size, true),
expected: datasetMappings[datasetName](size, !extra),
};
}
function main({ size, n, datasetName, extraProps }) {
const { actual, expected } = getDatasets(datasetName, size, extraProps);
bench.start();
for (let i = 0; i < n; ++i) {
assert.partialDeepStrictEqual(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
method: ['rejects', 'doesNotReject'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around a native promise
// with a few extra checks.
return p.n === 1;
},
});
async function main({ n, method }) {
const fn = assert[method];
const shouldReject = method === 'rejects';
bench.start();
for (let i = 0; i < n; ++i) {
await fn(async () => {
const err = new Error(`assert.${method}`);
if (shouldReject) {
throw err;
} else {
return err;
}
});
}
bench.end(n);
}

View File

@ -0,0 +1,49 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
type: ['string', 'object', 'number'],
method: ['strictEqual', 'notStrictEqual'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around `Object.is()`.
return p.n === 1;
},
});
function main({ type, n, method }) {
const fn = assert[method];
let actual, expected;
switch (type) {
case 'string':
actual = expected = 'Hello World';
if (method === 'notStrictEqual') {
expected += 'bar';
}
break;
case 'object':
actual = expected = { a: 'Hello', b: 'World' };
if (method === 'notStrictEqual') {
expected = { a: 'Hello', b: 'World' };
}
break;
case 'number':
actual = expected = 1e9;
if (method === 'notStrictEqual') {
expected += 1;
}
break;
default:
throw new Error('Unexpected type');
}
bench.start();
for (let i = 0; i < n; ++i) {
fn(actual, expected);
}
bench.end(n);
}

View File

@ -0,0 +1,33 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [2e5],
method: ['throws', 'doesNotThrow'],
}, {
combinationFilter(p) {
// These benchmarks purposefully do not run by default. They do not provide
// much insight, due to only being a small wrapper around a try / catch.
return p.n === 1;
},
});
function main({ n, method }) {
const fn = assert[method];
const shouldThrow = method === 'throws';
bench.start();
for (let i = 0; i < n; ++i) {
fn(() => {
const err = new Error(`assert.${method}`);
if (shouldThrow) {
throw err;
} else {
return err;
}
});
}
bench.end(n);
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage, AsyncResource } = require('async_hooks');
/**
* This benchmark verifies the performance of
* `AsyncLocalStorage.getStore()` on propagation through async
* resource scopes.
*
* - AsyncLocalStorage.run()
* - AsyncResource.runInAsyncScope
* - AsyncResource.runInAsyncScope
* ...
* - AsyncResource.runInAsyncScope
* - AsyncLocalStorage.getStore()
*/
const bench = common.createBenchmark(main, {
resourceCount: [10, 100, 1000],
n: [5e5],
});
function runBenchmark(store, n) {
for (let i = 0; i < n; i++) {
store.getStore();
}
}
function runInAsyncScopes(resourceCount, cb, i = 0) {
if (i === resourceCount) {
cb();
} else {
const resource = new AsyncResource('noop');
resource.runInAsyncScope(() => {
runInAsyncScopes(resourceCount, cb, i + 1);
});
}
}
function main({ n, resourceCount }) {
const store = new AsyncLocalStorage();
store.run({}, () => {
runInAsyncScopes(resourceCount, () => {
bench.start();
runBenchmark(store, n);
bench.end(n);
});
});
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
/**
* This benchmark verifies the performance of
* `AsyncLocalStorage.getStore()` on multiple `AsyncLocalStorage` instances
* nested `AsyncLocalStorage.run()`s.
*
* - AsyncLocalStorage1.run()
* - AsyncLocalStorage2.run()
* ...
* - AsyncLocalStorageN.run()
* - AsyncLocalStorage1.getStore()
*/
const bench = common.createBenchmark(main, {
storageCount: [1, 10, 100],
n: [1e4],
});
function runBenchmark(store, n) {
for (let idx = 0; idx < n; idx++) {
store.getStore();
}
}
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(stores[0], n);
bench.end(n);
});
}

View File

@ -0,0 +1,46 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage, AsyncResource } = require('async_hooks');
/**
* This benchmark verifies the performance degradation of
* async resource propagation on the increasing number of
* active `AsyncLocalStorage`s.
*
* - AsyncLocalStorage.run() * storageCount
* - new AsyncResource()
* - new AsyncResource()
* ...
* - N new Asyncresource()
*/
const bench = common.createBenchmark(main, {
storageCount: [0, 1, 10, 100],
n: [1e3],
});
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
function runBenchmark(n) {
for (let i = 0; i < n; i++) {
new AsyncResource('noop');
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(n);
bench.end(n);
});
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
/**
* This benchmark verifies the performance degradation of
* async resource propagation on the increasing number of
* active `AsyncLocalStorage`s.
*
* - AsyncLocalStorage.run()
* - Promise
* - Promise
* ...
* - Promise
*/
const bench = common.createBenchmark(main, {
storageCount: [0, 1, 10, 100],
n: [1e5],
});
function runStores(stores, value, cb, idx = 0) {
if (idx === stores.length) {
cb();
} else {
stores[idx].run(value, () => {
runStores(stores, value, cb, idx + 1);
});
}
}
async function runBenchmark(n) {
for (let i = 0; i < n; i++) {
// Avoid creating additional ticks.
await undefined;
}
}
function main({ n, storageCount }) {
const stores = new Array(storageCount).fill(0).map(() => new AsyncLocalStorage());
const contextValue = {};
runStores(stores, contextValue, () => {
bench.start();
runBenchmark(n).then(() => {
bench.end(n);
});
});
}

View File

@ -0,0 +1,21 @@
'use strict';
const common = require('../common.js');
const { AsyncLocalStorage } = require('async_hooks');
const bench = common.createBenchmark(main, {
n: [1e7],
});
async function run(store, n) {
for (let i = 0; i < n; i++) {
await new Promise((resolve) => store.run(i, resolve));
}
}
function main({ n }) {
const store = new AsyncLocalStorage();
bench.start();
run(store, n).then(() => {
bench.end(n);
});
}

View File

@ -0,0 +1,186 @@
'use strict';
const { promisify } = require('util');
const { readFile } = require('fs');
const sleep = promisify(setTimeout);
const read = promisify(readFile);
const common = require('../common.js');
const {
createHook,
executionAsyncResource,
executionAsyncId,
AsyncLocalStorage,
} = require('async_hooks');
const { createServer } = require('http');
const bench = common.createBenchmark(main, {
type: ['async-resource', 'destroy', 'async-local-storage'],
asyncMethod: ['callbacks', 'async'],
path: '/',
connections: 500,
duration: 5,
n: [1e6],
});
function buildCurrentResource(getServe) {
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init });
const cls = Symbol('cls');
hook.enable();
return {
server,
close,
};
function getCLS() {
const resource = executionAsyncResource();
if (!resource[cls]) {
return null;
}
return resource[cls].state;
}
function setCLS(state) {
const resource = executionAsyncResource();
if (!resource[cls]) {
resource[cls] = { state };
} else {
resource[cls].state = state;
}
}
function init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
if (cr !== null) {
resource[cls] = cr[cls];
}
}
function close() {
hook.disable();
server.close();
}
}
function buildDestroy(getServe) {
const transactions = new Map();
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init, destroy });
hook.enable();
return {
server,
close,
};
function getCLS() {
const asyncId = executionAsyncId();
return transactions.has(asyncId) ? transactions.get(asyncId) : null;
}
function setCLS(value) {
const asyncId = executionAsyncId();
transactions.set(asyncId, value);
}
function init(asyncId, type, triggerAsyncId, resource) {
transactions.set(asyncId, getCLS());
}
function destroy(asyncId) {
transactions.delete(asyncId);
}
function close() {
hook.disable();
server.close();
}
}
function buildAsyncLocalStorage(getServe) {
const asyncLocalStorage = new AsyncLocalStorage();
const server = createServer((req, res) => {
asyncLocalStorage.run({}, () => {
getServe(getCLS, setCLS)(req, res);
});
});
return {
server,
close,
};
function getCLS() {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return null;
}
return store.state;
}
function setCLS(state) {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return;
}
store.state = state;
}
function close() {
asyncLocalStorage.disable();
server.close();
}
}
function getServeAwait(getCLS, setCLS) {
return async function serve(req, res) {
setCLS(Math.random());
await sleep(10);
await read(__filename);
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
};
}
function getServeCallbacks(getCLS, setCLS) {
return function serve(req, res) {
setCLS(Math.random());
setTimeout(() => {
readFile(__filename, () => {
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
});
}, 10);
};
}
const types = {
'async-resource': buildCurrentResource,
'destroy': buildDestroy,
'async-local-storage': buildAsyncLocalStorage,
};
const asyncMethods = {
'callbacks': getServeCallbacks,
'async': getServeAwait,
};
function main({ type, asyncMethod, connections, duration, path }) {
const { server, close } = types[type](asyncMethods[asyncMethod]);
server
.listen(common.PORT)
.on('listening', () => {
bench.http({
path,
connections,
duration,
}, () => {
close();
});
});
}

View File

@ -0,0 +1,52 @@
'use strict';
const common = require('../common.js');
const { createHook, AsyncResource } = require('async_hooks');
const bench = common.createBenchmark(main, {
n: [1e6],
method: [
'trackingEnabled',
'trackingEnabledWithDestroyHook',
'trackingDisabled',
],
}, {
flags: ['--expose-gc'],
});
function endAfterGC(n) {
setImmediate(() => {
global.gc();
setImmediate(() => {
bench.end(n);
});
});
}
function main({ n, method }) {
switch (method) {
case 'trackingEnabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar');
}
endAfterGC(n);
break;
case 'trackingEnabledWithDestroyHook':
createHook({ destroy: () => {} }).enable();
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar');
}
endAfterGC(n);
break;
case 'trackingDisabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar', { requireManualDestroy: true });
}
endAfterGC(n);
break;
default:
throw new Error(`Unsupported method "${method}"`);
}
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
asyncHooks: ['init', 'before', 'after', 'all', 'disabled', 'none'],
connections: [50, 500],
duration: 5,
});
function main({ asyncHooks, connections, duration }) {
if (asyncHooks !== 'none') {
let hooks = {
init() {},
before() {},
after() {},
destroy() {},
promiseResolve() {},
};
if (asyncHooks !== 'all' || asyncHooks !== 'disabled') {
hooks = {
[asyncHooks]: () => {},
};
}
const hook = require('async_hooks').createHook(hooks);
if (asyncHooks !== 'disabled') {
hook.enable();
}
}
const server = require('../fixtures/simple-http-server.js')
.listen(common.PORT)
.on('listening', () => {
const path = '/buffer/4/4/normal/1';
bench.http({
connections,
path,
duration,
}, () => {
server.close();
});
});
}

View File

@ -0,0 +1,56 @@
'use strict';
const common = require('../common.js');
const { createHook } = require('async_hooks');
let hook;
const tests = {
disabled() {
hook = createHook({
promiseResolve() {},
});
},
enabled() {
hook = createHook({
promiseResolve() {},
}).enable();
},
enabledWithDestroy() {
hook = createHook({
promiseResolve() {},
destroy() {},
}).enable();
},
enabledWithInitOnly() {
hook = createHook({
init() {},
}).enable();
},
};
const bench = common.createBenchmark(main, {
n: [1e6],
asyncHooks: [
'enabled',
'enabledWithDestroy',
'enabledWithInitOnly',
'disabled',
],
});
const err = new Error('foobar');
async function run(n) {
for (let i = 0; i < n; i++) {
await new Promise((resolve) => resolve())
.then(() => { throw err; })
.catch((e) => e);
}
}
function main({ n, asyncHooks }) {
if (hook) hook.disable();
tests[asyncHooks]();
bench.start();
run(n).then(() => {
bench.end(n);
});
}

36
benchmark/bar.R Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env Rscript
library(ggplot2);
library(plyr);
# get __dirname and load ./_cli.R
args = commandArgs(trailingOnly = F);
dirname = dirname(sub("--file=", "", args[grep("--file", args)]));
source(paste0(dirname, '/_cli.R'), chdir=T);
if (!is.null(args.options$help) ||
(!is.null(args.options$plot) && args.options$plot == TRUE)) {
stop("usage: cat file.csv | Rscript bar.R
--help show this message
--plot filename save plot to filename");
}
plot.filename = args.options$plot;
dat = read.csv(
file('stdin'),
colClasses=c('character', 'character', 'character', 'numeric', 'numeric')
);
dat = data.frame(dat);
dat$nameTwoLines = paste0(dat$filename, '\n', dat$configuration);
dat$name = paste0(dat$filename, ' ', dat$configuration);
# Create a box plot
if (!is.null(plot.filename)) {
p = ggplot(data=dat, aes(x=nameTwoLines, y=rate, fill=binary));
p = p + geom_bar(stat="summary", position=position_dodge());
p = p + ylab("rate of operations (higher is better)");
p = p + xlab("benchmark");
p = p + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5));
ggsave(plot.filename, p);
}

30
benchmark/blob/blob.js Normal file
View File

@ -0,0 +1,30 @@
'use strict';
const common = require('../common.js');
const { Blob } = require('buffer');
const bench = common.createBenchmark(main, {
bytes: [128, 1024, 8192],
n: [1e3],
operation: ['text', 'arrayBuffer'],
});
async function run(n, bytes, operation) {
const buff = Buffer.allocUnsafe(bytes);
const source = new Blob(buff);
bench.start();
for (let i = 0; i < n; i++) {
switch (operation) {
case 'text':
await source.text();
break;
case 'arrayBuffer':
await source.arrayBuffer();
break;
}
}
bench.end(n);
}
function main(conf) {
run(conf.n, conf.bytes, conf.operation).catch(console.log);
}

25
benchmark/blob/clone.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common.js');
const {
Blob,
} = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
bytes: [128, 1024, 1024 ** 2],
});
let _cloneResult;
function main({ n, bytes }) {
const buff = Buffer.allocUnsafe(bytes);
const blob = new Blob(buff);
bench.start();
for (let i = 0; i < n; ++i)
_cloneResult = structuredClone(blob);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_cloneResult);
}

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common.js');
const {
Blob,
} = require('node:buffer');
const assert = require('assert');
const options = {
flags: ['--expose-internals'],
};
const bench = common.createBenchmark(main, {
n: [50e3],
kind: [
'Blob',
'createBlob',
],
}, options);
let _blob;
let _createBlob;
function main({ n, kind }) {
switch (kind) {
case 'Blob': {
bench.start();
for (let i = 0; i < n; ++i)
_blob = new Blob(['hello']);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_blob);
break;
} case 'createBlob': {
const { createBlob } = require('internal/blob');
const reusableBlob = new Blob(['hello']);
bench.start();
for (let i = 0; i < n; ++i)
_createBlob = createBlob(reusableBlob, reusableBlob.size);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_createBlob);
break;
} default:
throw new Error('Invalid kind');
}
}

34
benchmark/blob/file.js Normal file
View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common.js');
const { File } = require('buffer');
const bench = common.createBenchmark(main, {
bytes: [128, 1024],
n: [1e3],
operation: ['text', 'arrayBuffer'],
});
const options = {
lastModified: Date.now() - 1e6,
};
async function run(n, bytes, operation) {
const buff = Buffer.allocUnsafe(bytes);
const source = new File(buff, 'dummy.txt', options);
bench.start();
for (let i = 0; i < n; i++) {
switch (operation) {
case 'text':
await source.text();
break;
case 'arrayBuffer':
await source.arrayBuffer();
break;
}
}
bench.end(n);
}
function main(conf) {
run(conf.n, conf.bytes, conf.operation).catch(console.log);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const { Blob, resolveObjectURL } = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
});
let _resolveObjectURL;
function main({ n }) {
const blob = new Blob(['hello']);
const id = URL.createObjectURL(blob);
bench.start();
for (let i = 0; i < n; ++i)
_resolveObjectURL = resolveObjectURL(id);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_resolveObjectURL);
}

27
benchmark/blob/slice.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const common = require('../common.js');
const { Blob } = require('node:buffer');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [50e3],
dataSize: [
5,
30 * 1024,
],
});
let _sliceResult;
function main({ n, dataSize }) {
const data = 'a'.repeat(dataSize);
const reusableBlob = new Blob([data]);
bench.start();
for (let i = 0; i < n; ++i)
_sliceResult = reusableBlob.slice(0, Math.floor(data.length / 2));
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_sliceResult);
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
size: [16, 32, 64, 128],
n: [1e6],
});
function main({ n, size }) {
const input = btoa('A'.repeat(size));
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
out += atob(input).length;
}
bench.end(n);
assert.ok(out > 0);
}

View File

@ -0,0 +1,25 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
charsPerLine: [76],
linesCount: [8 << 16],
n: [32],
});
function main({ charsPerLine, linesCount, n }) {
const bytesCount = charsPerLine * linesCount / 4 * 3;
const line = `${'abcd'.repeat(charsPerLine / 4)}\n`;
const data = line.repeat(linesCount);
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
data.match(/./); // Flatten the string
const buffer = Buffer.alloc(bytesCount, line, 'base64');
bench.start();
for (let i = 0; i < n; i++) {
buffer.base64Write(data, 0, bytesCount);
}
bench.end(n);
}

View File

@ -0,0 +1,29 @@
'use strict';
const assert = require('assert');
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [32],
size: [8 << 20],
});
function main({ n, size }) {
const s = 'abcd'.repeat(size);
const encodedSize = s.length * 3 / 4;
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
s.match(/./); // Flatten string.
assert.strictEqual(s.length % 4, 0);
const b = Buffer.allocUnsafe(encodedSize);
b.write(s, 0, encodedSize, 'base64');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = b.base64Write(s, 0, s.length);
bench.end(n);
assert.strictEqual(tmp, encodedSize);
}

View File

@ -0,0 +1,50 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64 * 1024 * 1024],
n: [32],
}, {
test: { len: 256 },
});
function main({ n, len }) {
const b = Buffer.allocUnsafe(len);
let s = '';
let i;
for (i = 0; i < 256; ++i) s += String.fromCharCode(i);
for (i = 0; i < len; i += 256) b.write(s, i, 256, 'ascii');
let tmp;
bench.start();
for (i = 0; i < n; ++i)
tmp = b.toString('base64');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,29 @@
'use strict';
const assert = require('assert');
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [32],
size: [8 << 20],
});
function main({ n, size }) {
const s = 'abcd'.repeat(size);
const encodedSize = s.length * 3 / 4;
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
s.match(/./); // Flatten string.
assert.strictEqual(s.length % 4, 0);
const b = Buffer.allocUnsafe(encodedSize);
b.write(s, 0, encodedSize, 'base64url');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = b.base64Write(s, 0, s.length);
bench.end(n);
assert.strictEqual(tmp, encodedSize);
}

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64 * 1024 * 1024],
n: [32],
}, {
test: { len: 256 },
});
function main({ n, len }) {
const b = Buffer.allocUnsafe(len);
let s = '';
let i;
for (i = 0; i < 256; ++i) s += String.fromCharCode(i);
for (i = 0; i < len; i += 256) b.write(s, i, 256, 'ascii');
let tmp;
bench.start();
for (i = 0; i < n; ++i)
tmp = b.toString('base64url');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
size: [16, 32, 64, 128, 256, 1024],
n: [1e6],
});
function main({ n, size }) {
const input = 'A'.repeat(size);
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
out += btoa(input).length;
}
bench.end(n);
assert.ok(out > 0);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const bench = common.createBenchmark(main, {
len: [2, 16, 256], // x16
n: [4e6],
});
function main({ n, len }) {
const data = Buffer.alloc(len * 16, 'a');
const expected = Buffer.byteLength(data, 'buffer');
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, 'buffer');
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}

View File

@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
const bench = common.createBenchmark(main, {
type: ['one_byte', 'two_bytes', 'three_bytes',
'four_bytes', 'latin1'],
encoding: ['utf8', 'base64'],
repeat: [1, 2, 16, 256], // x16
n: [4e6],
});
// 16 chars each
const chars = {
one_byte: 'hello brendan!!!',
two_bytes: 'ΰαβγδεζηθικλμνξο',
three_bytes: '挰挱挲挳挴挵挶挷挸挹挺挻挼挽挾挿',
four_bytes: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢',
latin1: 'Un homme sage est supérieur à toutes ' +
'les insultes qui peuvent lui être adressées, et la meilleure réponse est la patience et la modération.',
};
function getInput(type, repeat, encoding) {
const original = (repeat === 1) ? chars[type] : chars[type].repeat(repeat);
if (encoding === 'base64') {
Buffer.from(original, 'utf8').toString('base64');
}
return original;
}
function main({ n, repeat, encoding, type }) {
const data = getInput(type, repeat, encoding);
const expected = Buffer.byteLength(data, encoding);
let changed = false;
bench.start();
for (let i = 0; i < n; i++) {
const actual = Buffer.byteLength(data, encoding);
if (expected !== actual) { changed = true; }
}
bench.end(n);
if (changed) {
throw new Error('Result changed during iteration');
}
}

View File

@ -0,0 +1,59 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [16, 512, 4096, 16386],
args: [1, 2, 5],
n: [1e6],
});
function main({ n, size, args }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size, 'a');
const b0Len = b0.length;
const b1Len = b1.length;
b1[size - 1] = 'b'.charCodeAt(0);
switch (args) {
case 2:
b0.compare(b1, 0);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0);
}
bench.end(n);
break;
case 3:
b0.compare(b1, 0, b1Len);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len);
}
bench.end(n);
break;
case 4:
b0.compare(b1, 0, b1Len, 0);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len, 0);
}
bench.end(n);
break;
case 5:
b0.compare(b1, 0, b1Len, 0, b0Len);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1, 0, b1Len, 0, b0Len);
}
bench.end(n);
break;
default:
b0.compare(b1);
bench.start();
for (let i = 0; i < n; i++) {
b0.compare(b1);
}
bench.end(n);
}
}

View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
method: ['offset', 'slice'],
size: [16, 512, 4096, 16386],
n: [1e6],
});
function compareUsingSlice(b0, b1, len, iter) {
for (let i = 0; i < iter; i++)
Buffer.compare(b0.slice(1, len), b1.slice(1, len));
}
function compareUsingOffset(b0, b1, len, iter) {
for (let i = 0; i < iter; i++)
b0.compare(b1, 1, len, 1, len);
}
function main({ n, size, method }) {
const fn = method === 'slice' ? compareUsingSlice : compareUsingOffset;
bench.start();
fn(Buffer.alloc(size, 'a'),
Buffer.alloc(size, 'b'),
size >> 1,
n);
bench.end(n);
}

View File

@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [16, 512, 4096, 16386],
n: [1e6],
});
function main({ n, size }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size, 'a');
b1[size - 1] = 'b'.charCodeAt(0);
bench.start();
for (let i = 0; i < n; i++) {
Buffer.compare(b0, b1);
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
extraSize: [1, 256, 4 * 256],
n: [8e5],
});
function main({ n, extraSize }) {
const pieces = 4;
const pieceSize = 256;
const list = Array.from({ length: pieces })
.fill(Buffer.allocUnsafe(pieceSize));
const totalLength = (pieces * pieceSize) + extraSize;
bench.start();
for (let i = 0; i < n; i++) {
Buffer.concat(list, totalLength);
}
bench.end(n);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
pieces: [4, 16],
pieceSize: [1, 16, 256],
withTotalLength: [0, 1],
n: [8e5],
});
function main({ n, pieces, pieceSize, withTotalLength }) {
const list = Array.from({ length: pieces })
.fill(Buffer.allocUnsafe(pieceSize));
const totalLength = withTotalLength ? pieces * pieceSize : undefined;
bench.start();
for (let i = 0; i < n; i++) {
Buffer.concat(list, totalLength);
}
bench.end(n);
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
bytes: [8, 128, 1024],
partial: ['true', 'false'],
n: [6e6],
});
function main({ n, bytes, partial }) {
const source = Buffer.allocUnsafe(bytes);
const target = Buffer.allocUnsafe(bytes);
const sourceStart = (partial === 'true' ? Math.floor(bytes / 2) : 0);
bench.start();
for (let i = 0; i < n; i++) {
source.copy(target, 0, sourceStart);
}
bench.end(n);
}

View File

@ -0,0 +1,44 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
type: [
'fast-alloc',
'fast-alloc-fill',
'fast-allocUnsafe',
'slow-allocUnsafe',
],
len: [10, 1024, 4096, 8192],
n: [6e5],
});
function main({ len, n, type }) {
let fn, i;
switch (type) {
case 'fast-alloc':
fn = Buffer.alloc;
break;
case 'fast-alloc-fill':
bench.start();
for (i = 0; i < n; i++) {
Buffer.alloc(len, 0);
}
bench.end(n);
return;
case 'fast-allocUnsafe':
fn = Buffer.allocUnsafe;
break;
case 'slow-allocUnsafe':
fn = Buffer.allocUnsafeSlow;
break;
default:
assert.fail('Should not get here');
}
bench.start();
for (i = 0; i < n; i++) {
fn(len);
}
bench.end(n);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
size: [0, 512, 16386],
difflen: ['true', 'false'],
n: [1e6],
});
function main({ n, size, difflen }) {
const b0 = Buffer.alloc(size, 'a');
const b1 = Buffer.alloc(size + (difflen === 'true' ? 1 : 0), 'a');
if (b1.length > 0)
b1[b1.length - 1] = 'b'.charCodeAt(0);
bench.start();
for (let i = 0; i < n; i++) {
b0.equals(b1);
}
bench.end(n);
}

View File

@ -0,0 +1,31 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
type: [
'fill(0)',
'fill("")',
'fill(100)',
'fill(400)',
'fill("t")',
'fill("test")',
'fill("t", "utf8")',
'fill("t", 0, "utf8")',
'fill("t", 0)',
'fill(Buffer.alloc(1), 0)',
],
size: [2 ** 13, 2 ** 16],
n: [2e4],
});
function main({ n, type, size }) {
const buffer = Buffer.allocUnsafe(size);
const testFunction = new Function('b', `
for (var i = 0; i < ${n}; i++) {
b.${type};
}
`);
bench.start();
testFunction(buffer);
bench.end(n);
}

View File

@ -0,0 +1,121 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
source: [
'array',
'arraybuffer',
'arraybuffer-middle',
'buffer',
'string',
'string-utf8',
'string-base64',
'object',
'uint8array',
'uint16array',
],
len: [100, 2048],
n: [8e5],
});
function main({ len, n, source }) {
let i = 0;
switch (source) {
case 'array': {
const array = new Array(len).fill(42);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(array);
}
bench.end(n);
break;
}
case 'arraybuffer': {
const arrayBuf = new ArrayBuffer(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(arrayBuf);
}
bench.end(n);
break;
}
case 'arraybuffer-middle': {
const arrayBuf = new ArrayBuffer(len);
const offset = ~~(len / 4);
const length = ~~(len / 2);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(arrayBuf, offset, length);
}
bench.end(n);
break;
}
case 'buffer': {
const buffer = Buffer.allocUnsafe(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(buffer);
}
bench.end(n);
break;
}
case 'uint8array': {
const uint8array = new Uint8Array(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(uint8array);
}
bench.end(n);
break;
}
case 'uint16array': {
const uint16array = new Uint16Array(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(uint16array);
}
bench.end(n);
break;
}
case 'string': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str);
}
bench.end(n);
break;
}
case 'string-utf8': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str, 'utf8');
}
bench.end(n);
break;
}
case 'string-base64': {
const str = 'a'.repeat(len);
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(str, 'base64');
}
bench.end(n);
break;
}
case 'object': {
const obj = { length: null }; // Results in a new, empty Buffer
bench.start();
for (i = 0; i < n; i++) {
Buffer.from(obj);
}
bench.end(n);
break;
}
default:
assert.fail('Should not get here');
}
}

View File

@ -0,0 +1,29 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64, 1024],
n: [1e6],
});
function main({ len, n }) {
const buf = Buffer.alloc(len);
for (let i = 0; i < buf.length; i++)
buf[i] = i & 0xff;
const plain = buf;
bench.start();
let tmp;
for (let i = 0; i < n; i += 1)
tmp = plain.toString('hex');
bench.end(n);
assert.strictEqual(typeof tmp, 'string');
}

View File

@ -0,0 +1,28 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
len: [64, 1024],
n: [1e6],
});
function main({ len, n }) {
const buf = Buffer.alloc(len);
for (let i = 0; i < buf.length; i++)
buf[i] = i & 0xff;
const hex = buf.toString('hex');
let tmp;
bench.start();
for (let i = 0; i < n; i += 1)
tmp = Buffer.from(hex, 'hex');
bench.end(n);
assert.strictEqual(typeof tmp, 'object');
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const fs = require('fs');
const path = require('path');
const bench = common.createBenchmark(main, {
value: ['@'.charCodeAt(0)],
n: [1e6],
});
function main({ n, value }) {
const aliceBuffer = fs.readFileSync(
path.resolve(__dirname, '../fixtures/alice.html'),
);
let count = 0;
bench.start();
for (let i = 0; i < n; i++) {
count += aliceBuffer.indexOf(value, 0, undefined);
}
bench.end(n);
return count;
}

View File

@ -0,0 +1,54 @@
'use strict';
const common = require('../common.js');
const fs = require('fs');
const path = require('path');
const searchStrings = [
'@',
'SQ',
'--l',
'Alice',
'Gryphon',
'Ou est ma chatte?',
'found it very',
'neighbouring pool',
'aaaaaaaaaaaaaaaaa',
'venture to go near the house till she had brought herself down to',
'</i> to the Caterpillar',
];
const bench = common.createBenchmark(main, {
search: searchStrings,
encoding: ['undefined', 'utf8', 'ucs2'],
type: ['buffer', 'string'],
n: [5e4],
}, {
combinationFilter: (p) => {
return (p.type === 'buffer' && p.encoding === 'undefined') ||
(p.type !== 'buffer' && p.encoding !== 'undefined');
},
});
function main({ n, search, encoding, type }) {
let aliceBuffer = fs.readFileSync(
path.resolve(__dirname, '../fixtures/alice.html'),
);
if (encoding === 'undefined') {
encoding = undefined;
}
if (encoding === 'ucs2') {
aliceBuffer = Buffer.from(aliceBuffer.toString(), encoding);
}
if (type === 'buffer') {
search = Buffer.from(Buffer.from(search).toString(), encoding);
}
bench.start();
for (let i = 0; i < n; i++) {
aliceBuffer.indexOf(search, 0, encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const buffer = require('node:buffer');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
n: [2e7],
length: ['short', 'long'],
input: ['hello world'],
});
function main({ n, input }) {
const normalizedInput = input === 'short' ? input : input.repeat(200);
const encoder = new TextEncoder();
const buff = encoder.encode(normalizedInput);
bench.start();
for (let i = 0; i < n; ++i) {
assert.ok(buffer.isAscii(buff));
}
bench.end(n);
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const buffer = require('node:buffer');
const assert = require('node:assert');
const bench = common.createBenchmark(main, {
n: [2e7],
length: ['short', 'long'],
input: ['regular string', '∀x∈: ⌈x⌉ = x⌋'],
});
function main({ n, input, length }) {
const normalizedInput = length === 'short' ? input : input.repeat(300);
const encoder = new TextEncoder();
const buff = encoder.encode(normalizedInput);
bench.start();
for (let i = 0; i < n; ++i) {
assert.ok(buffer.isUtf8(buff));
}
bench.end(n);
}

View File

@ -0,0 +1,58 @@
'use strict';
const SlowBuffer = require('buffer').SlowBuffer;
const common = require('../common.js');
const assert = require('assert');
const bench = common.createBenchmark(main, {
size: [512, 4096, 16386],
type: ['fast'],
method: ['for', 'forOf', 'iterator'],
n: [1e3],
});
const methods = {
'for': benchFor,
'forOf': benchForOf,
'iterator': benchIterator,
};
function main({ size, type, method, n }) {
const buffer = type === 'fast' ?
Buffer.alloc(size) :
SlowBuffer(size).fill(0);
const fn = methods[method];
bench.start();
fn(buffer, n);
bench.end(n);
}
function benchFor(buffer, n) {
for (let k = 0; k < n; k++) {
for (let i = 0; i < buffer.length; i++) {
assert.strictEqual(buffer[i], 0);
}
}
}
function benchForOf(buffer, n) {
for (let k = 0; k < n; k++) {
for (const b of buffer) {
assert.strictEqual(b, 0);
}
}
}
function benchIterator(buffer, n) {
for (let k = 0; k < n; k++) {
const iter = buffer[Symbol.iterator]();
let cur = iter.next();
while (!cur.done) {
assert.strictEqual(cur.value, 0);
cur = iter.next();
}
}
}

View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'ascii',
'base64',
'BASE64',
'binary',
'hex',
'HEX',
'latin1',
'LATIN1',
'UCS-2',
'UCS2',
'utf-16le',
'UTF-16LE',
'utf-8',
'utf16le',
'UTF16LE',
'utf8',
'UTF8',
],
n: [1e6],
}, {
flags: ['--expose-internals'],
});
function main({ encoding, n }) {
const { normalizeEncoding } = require('internal/util');
bench.start();
for (let i = 0; i < n; i++) {
normalizeEncoding(encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
type: ['Double', 'Float'],
endian: ['LE'],
value: ['zero', 'big', 'small', 'inf', 'nan'],
n: [1e6],
});
function main({ n, type, endian, value }) {
const buff = Buffer.alloc(8);
const fn = `read${type}${endian}`;
const values = {
Double: {
zero: 0,
big: 2 ** 1023,
small: 2 ** -1074,
inf: Infinity,
nan: NaN,
},
Float: {
zero: 0,
big: 2 ** 127,
small: 2 ** -149,
inf: Infinity,
nan: NaN,
},
};
buff[`write${type}${endian}`](values[type][value], 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0);
}
bench.end(n);
}

View File

@ -0,0 +1,30 @@
'use strict';
const common = require('../common.js');
const types = [
'IntBE',
'IntLE',
'UIntBE',
'UIntLE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
byteLength: [1, 2, 3, 4, 5, 6],
});
function main({ n, buf, type, byteLength }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `read${type}`;
buff.writeDoubleLE(0, 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0, byteLength);
}
bench.end(n);
}

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common.js');
const types = [
'BigUInt64LE',
'BigUInt64BE',
'BigInt64LE',
'BigInt64BE',
'UInt8',
'UInt16LE',
'UInt16BE',
'UInt32LE',
'UInt32BE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
});
function main({ n, buf, type }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `read${type}`;
buff.writeDoubleLE(0, 0);
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](0);
}
bench.end(n);
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common.js');
const SlowBuffer = require('buffer').SlowBuffer;
const bench = common.createBenchmark(main, {
type: ['fast', 'slow', 'subarray'],
n: [1e6],
});
const buf = Buffer.allocUnsafe(1024);
const slowBuf = new SlowBuffer(1024);
function main({ n, type }) {
const b = type === 'slow' ? slowBuf : buf;
const fn = type === 'subarray' ?
() => b.subarray(10, 256) :
() => b.slice(10, 256);
bench.start();
for (let i = 0; i < n; i++) {
fn();
}
bench.end(n);
}

View File

@ -0,0 +1,85 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
aligned: ['true', 'false'],
method: ['swap16', 'swap32', 'swap64'/* , 'htons', 'htonl', 'htonll' */],
len: [64, 256, 768, 1024, 2056, 8192],
n: [1e6],
}, {
test: { len: 16 },
});
// The htons and htonl methods below are used to benchmark the
// performance difference between doing the byteswap in pure
// javascript regardless of Buffer size as opposed to dropping
// down to the native layer for larger Buffer sizes. Commented
// out by default because they are slow for big buffers. If
// re-evaluating the crossover point, uncomment those methods
// and comment out their implementations in lib/buffer.js so
// C++ version will always be used.
function swap(b, n, m) {
const i = b[n];
b[n] = b[m];
b[m] = i;
}
Buffer.prototype.htons = function htons() {
if (this.length % 2 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 2) {
swap(this, i, i + 1);
}
return this;
};
Buffer.prototype.htonl = function htonl() {
if (this.length % 4 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 4) {
swap(this, i, i + 3);
swap(this, i + 1, i + 2);
}
return this;
};
Buffer.prototype.htonll = function htonll() {
if (this.length % 8 !== 0)
throw new RangeError();
for (let i = 0; i < this.length; i += 8) {
swap(this, i, i + 7);
swap(this, i + 1, i + 6);
swap(this, i + 2, i + 5);
swap(this, i + 3, i + 4);
}
return this;
};
function createBuffer(len, aligned) {
len += aligned ? 0 : 1;
const buf = Buffer.allocUnsafe(len);
for (let i = 1; i <= len; i++)
buf[i - 1] = i;
return aligned ? buf : buf.slice(1);
}
function genMethod(method) {
const fnString = `
return function ${method}(n, buf) {
for (let i = 0; i <= n; i++)
buf.${method}();
}`;
return (new Function(fnString))();
}
function main({ method, len, n, aligned = 'true' }) {
const buf = createBuffer(len, aligned === 'true');
const bufferSwap = genMethod(method);
bufferSwap(n, buf);
bench.start();
bufferSwap(n, buf);
bench.end(n);
}

View File

@ -0,0 +1,17 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1e4],
len: [0, 256, 4 * 1024],
});
function main({ n, len }) {
const buf = Buffer.allocUnsafe(len);
bench.start();
for (let i = 0; i < n; ++i)
buf.toJSON();
bench.end(n);
}

View File

@ -0,0 +1,49 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: ['', 'utf8', 'ascii', 'latin1', 'hex', 'UCS-2'],
args: [0, 1, 3],
len: [1, 64, 1024],
n: [1e6],
}, {
combinationFilter: (p) => {
return (p.args === 0 && p.encoding === '') ||
(p.args !== 0 && p.encoding !== '');
},
});
function main({ encoding, args, len, n }) {
const buf = Buffer.alloc(len, 42);
if (encoding.length === 0)
encoding = undefined;
switch (args) {
case 1:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding, 0);
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString(encoding, 0, len);
bench.end(n);
break;
default:
bench.start();
for (let i = 0; i < n; i += 1)
buf.toString();
bench.end(n);
break;
}
}

View File

@ -0,0 +1,35 @@
'use strict';
const common = require('../common.js');
const assert = require('node:assert');
const buffer = require('node:buffer');
const hasIntl = !!process.config.variables.v8_enable_i18n_support;
const encodings = ['latin1', 'ascii', 'ucs2', 'utf8'];
if (!hasIntl) {
console.log('Skipping: `transcode` is only available on platforms that support i18n`');
process.exit(0);
}
const bench = common.createBenchmark(main, {
fromEncoding: encodings,
toEncoding: encodings,
length: [1, 10, 1000],
n: [1e5],
}, {
combinationFilter(p) {
return !(p.fromEncoding === 'ucs2' && p.toEncoding === 'utf8');
},
});
function main({ n, fromEncoding, toEncoding, length }) {
const input = Buffer.from('a'.repeat(length));
let out = 0;
bench.start();
for (let i = 0; i < n; i++) {
const dest = buffer.transcode(input, fromEncoding, toEncoding);
out += dest.buffer.byteLength;
}
bench.end(n);
assert.ok(out >= 0);
}

View File

@ -0,0 +1,20 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'utf8', 'ascii', 'latin1',
],
len: [1, 8, 16, 32],
n: [1e6],
});
function main({ len, n, encoding }) {
const buf = Buffer.allocUnsafe(len);
const string = Buffer.from('a'.repeat(len)).toString();
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, encoding);
}
bench.end(n);
}

View File

@ -0,0 +1,68 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
encoding: [
'', 'utf8', 'ascii', 'hex', 'utf16le', 'latin1',
],
args: [ '', 'offset', 'offset+length' ],
len: [2048],
n: [1e6],
});
function main({ len, n, encoding, args }) {
let string;
let start = 0;
const buf = Buffer.allocUnsafe(len);
switch (args) {
case 'offset':
string = 'a'.repeat(Math.floor(len / 2));
start = len - string.length;
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, start, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, start);
}
bench.end(n);
}
break;
case 'offset+length':
string = 'a'.repeat(len);
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, buf.length, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, buf.length);
}
bench.end(n);
}
break;
default:
string = 'a'.repeat(len);
if (encoding) {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, encoding);
}
bench.end(n);
} else {
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string);
}
bench.end(n);
}
}
}

View File

@ -0,0 +1,123 @@
'use strict';
const common = require('../common.js');
const types = [
'BigUInt64LE',
'BigUInt64BE',
'BigInt64LE',
'BigInt64BE',
'UInt8',
'UInt16LE',
'UInt16BE',
'UInt32LE',
'UInt32BE',
'UIntLE',
'UIntBE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
'IntLE',
'IntBE',
'FloatLE',
'FloatBE',
'DoubleLE',
'DoubleBE',
];
const bench = common.createBenchmark(main, {
buffer: ['fast'],
type: types,
n: [1e6],
});
const INT8 = 0x7f;
const INT16 = 0x7fff;
const INT32 = 0x7fffffff;
const INT48 = 0x7fffffffffff;
const INT64 = 0x7fffffffffffffffn;
const UINT8 = 0xff;
const UINT16 = 0xffff;
const UINT32 = 0xffffffff;
const UINT64 = 0xffffffffffffffffn;
const mod = {
writeBigInt64BE: INT64,
writeBigInt64LE: INT64,
writeBigUInt64BE: UINT64,
writeBigUInt64LE: UINT64,
writeInt8: INT8,
writeInt16BE: INT16,
writeInt16LE: INT16,
writeInt32BE: INT32,
writeInt32LE: INT32,
writeUInt8: UINT8,
writeUInt16BE: UINT16,
writeUInt16LE: UINT16,
writeUInt32BE: UINT32,
writeUInt32LE: UINT32,
writeUIntLE: INT8,
writeUIntBE: INT16,
writeIntLE: INT32,
writeIntBE: INT48,
};
const byteLength = {
writeUIntLE: 1,
writeUIntBE: 2,
writeIntLE: 4,
writeIntBE: 6,
};
function main({ n, buf, type }) {
const buff = buf === 'fast' ?
Buffer.alloc(8) :
require('buffer').SlowBuffer(8);
const fn = `write${type}`;
if (!/\d/.test(fn))
benchSpecialInt(buff, fn, n);
else if (/BigU?Int/.test(fn))
benchBigInt(buff, fn, BigInt(n));
else if (/Int/.test(fn))
benchInt(buff, fn, n);
else
benchFloat(buff, fn, n);
}
function benchBigInt(buff, fn, n) {
const m = mod[fn];
bench.start();
for (let i = 0n; i !== n; i++) {
buff[fn](i & m, 0);
}
bench.end(Number(n));
}
function benchInt(buff, fn, n) {
const m = mod[fn];
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i & m, 0);
}
bench.end(n);
}
function benchSpecialInt(buff, fn, n) {
const m = mod[fn];
const byte = byteLength[fn];
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i & m, 0, byte);
}
bench.end(n);
}
function benchFloat(buff, fn, n) {
bench.start();
for (let i = 0; i !== n; i++) {
buff[fn](i, 0);
}
bench.end(n);
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1e6],
type: ['buffer', 'string'],
});
const zeroBuffer = Buffer.alloc(0);
const zeroString = '';
function main({ n, type }) {
const data = type === 'buffer' ? zeroBuffer : zeroString;
bench.start();
for (let i = 0; i < n; i++) Buffer.from(data);
bench.end(n);
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const types = [
'Uint8',
'Uint16LE',
'Uint16BE',
'Uint32LE',
'Uint32BE',
'Int8',
'Int16LE',
'Int16BE',
'Int32LE',
'Int32BE',
'Float32LE',
'Float32BE',
'Float64LE',
'Float64BE',
];
const bench = common.createBenchmark(main, {
type: types,
n: [1e6],
});
const INT8 = 0x7f;
const INT16 = 0x7fff;
const INT32 = 0x7fffffff;
const UINT8 = INT8 * 2;
const UINT16 = INT16 * 2;
const UINT32 = INT32 * 2;
const mod = {
setInt8: INT8,
setInt16: INT16,
setInt32: INT32,
setUint8: UINT8,
setUint16: UINT16,
setUint32: UINT32,
};
function main({ n, type }) {
const ab = new ArrayBuffer(8);
const dv = new DataView(ab, 0, 8);
const le = /LE$/.test(type);
const fn = `set${type.replace(/[LB]E$/, '')}`;
if (/int/i.test(fn))
benchInt(dv, fn, n, le);
else
benchFloat(dv, fn, n, le);
}
function benchInt(dv, fn, len, le) {
const m = mod[fn];
const method = dv[fn];
bench.start();
for (let i = 0; i < len; i++) {
method.call(dv, 0, i % m, le);
}
bench.end(len);
}
function benchFloat(dv, fn, len, le) {
const method = dv[fn];
bench.start();
for (let i = 0; i < len; i++) {
method.call(dv, 0, i * 0.1, le);
}
bench.end(len);
}

View File

@ -0,0 +1,40 @@
'use strict';
const common = require('../common.js');
const { exec, execSync } = require('child_process');
const isWindows = process.platform === 'win32';
const messagesLength = [64, 256, 1024, 4096];
// Windows does not support command lines longer than 8191 characters
if (!isWindows) messagesLength.push(32768);
const bench = common.createBenchmark(childProcessExecStdout, {
len: messagesLength,
dur: [5],
});
function childProcessExecStdout({ dur, len }) {
bench.start();
const maxDuration = dur * 1000;
const cmd = `yes "${'.'.repeat(len)}"`;
const child = exec(cmd, { 'stdio': ['ignore', 'pipe', 'ignore'] });
let bytes = 0;
child.stdout.on('data', (msg) => {
bytes += msg.length;
});
setTimeout(() => {
bench.end(bytes);
if (isWindows) {
// Sometimes there's a yes.exe process left hanging around on Windows.
try {
execSync(`taskkill /f /t /pid ${child.pid}`);
} catch {
// This is a best effort kill. stderr is piped to parent for tracing.
}
} else {
child.kill();
}
}, maxDuration);
}

View File

@ -0,0 +1,124 @@
'use strict';
const common = require('../common.js');
const cp = require('child_process');
const command = 'echo';
const args = ['hello'];
const options = {};
const cb = () => {};
const configs = {
n: [1e3],
methodName: [
'exec', 'execSync',
'execFile', 'execFileSync',
'spawn', 'spawnSync',
],
params: [1, 2, 3, 4],
};
const bench = common.createBenchmark(main, configs);
function main({ n, methodName, params }) {
const method = cp[methodName];
switch (methodName) {
case 'exec':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, options).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, options, cb).kill();
bench.end(n);
break;
}
break;
case 'execSync':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, options);
bench.end(n);
break;
}
break;
case 'execFile':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options).kill();
bench.end(n);
break;
case 4:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options, cb).kill();
bench.end(n);
break;
}
break;
case 'execFileSync':
case 'spawnSync':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command);
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args);
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options);
bench.end(n);
break;
}
break;
case 'spawn':
switch (params) {
case 1:
bench.start();
for (let i = 0; i < n; i++) method(command).kill();
bench.end(n);
break;
case 2:
bench.start();
for (let i = 0; i < n; i++) method(command, args).kill();
bench.end(n);
break;
case 3:
bench.start();
for (let i = 0; i < n; i++) method(command, args, options).kill();
bench.end(n);
break;
}
break;
}
}

View File

@ -0,0 +1,37 @@
'use strict';
if (process.argv[2] === 'child') {
const len = +process.argv[3];
const msg = '.'.repeat(len);
const send = () => {
while (process.send(msg));
// Wait: backlog of unsent messages exceeds threshold
setImmediate(send);
};
send();
} else {
const common = require('../common.js');
const bench = common.createBenchmark(main, {
len: [
64, 256, 1024, 4096, 16384, 65536,
65536 << 4, 65536 << 6 - 1,
],
dur: [5],
});
const spawn = require('child_process').spawn;
function main({ dur, len }) {
bench.start();
const options = { 'stdio': ['ignore', 1, 2, 'ipc'] };
const child = spawn(process.argv[0],
[process.argv[1], 'child', len], options);
let bytes = 0;
child.on('message', (msg) => { bytes += msg.length; });
setTimeout(() => {
child.kill();
bench.end(bytes);
}, dur * 1000);
}
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common.js');
// This benchmark uses `yes` to a create noisy child_processes with varying
// output message lengths, and tries to read 8GB of output
const os = require('os');
const child_process = require('child_process');
const messagesLength = [64, 256, 1024, 4096];
// Windows does not support that long arguments
if (os.platform() !== 'win32')
messagesLength.push(32768);
const bench = common.createBenchmark(main, {
len: messagesLength,
dur: [5],
});
function main({ dur, len }) {
bench.start();
const msg = `"${'.'.repeat(len)}"`;
const options = { 'stdio': ['ignore', 'pipe', 'ignore'] };
const child = child_process.spawn('yes', [msg], options);
let bytes = 0;
child.stdout.on('data', (msg) => {
bytes += msg.length;
});
setTimeout(() => {
if (process.platform === 'win32') {
// Sometimes there's a yes.exe process left hanging around on Windows...
child_process.execSync(`taskkill /f /t /pid ${child.pid}`);
} else {
child.kill();
}
const gbits = (bytes * 8) / (1024 * 1024 * 1024);
bench.end(gbits);
}, dur * 1000);
}

View File

@ -0,0 +1,24 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1000],
});
const spawn = require('child_process').spawn;
function main({ n }) {
bench.start();
go(n, n);
}
function go(n, left) {
if (--left === 0)
return bench.end(n);
const child = spawn('echo', ['hello']);
child.on('exit', (code) => {
if (code)
process.exit(code);
else
go(n, left);
});
}

75
benchmark/cluster/echo.js Normal file
View File

@ -0,0 +1,75 @@
'use strict';
const cluster = require('cluster');
if (cluster.isPrimary) {
const common = require('../common.js');
const bench = common.createBenchmark(main, {
workers: [1],
payload: ['string', 'object'],
sendsPerBroadcast: [1, 10],
serialization: ['json', 'advanced'],
n: [1e5],
});
function main({
n,
workers,
sendsPerBroadcast,
payload,
serialization,
}) {
const expectedPerBroadcast = sendsPerBroadcast * workers;
let readies = 0;
let broadcasts = 0;
let msgCount = 0;
let data;
cluster.settings.serialization = serialization;
switch (payload) {
case 'string':
data = 'hello world!';
break;
case 'object':
data = { action: 'pewpewpew', powerLevel: 9001 };
break;
default:
throw new Error('Unsupported payload type');
}
for (let i = 0; i < workers; ++i)
cluster.fork().on('online', onOnline).on('message', onMessage);
function onOnline() {
if (++readies === workers) {
bench.start();
broadcast();
}
}
function broadcast() {
if (broadcasts++ === n) {
bench.end(n);
for (const id in cluster.workers)
cluster.workers[id].disconnect();
return;
}
for (const id in cluster.workers) {
const worker = cluster.workers[id];
for (let i = 0; i < sendsPerBroadcast; ++i)
worker.send(data);
}
}
function onMessage() {
if (++msgCount === expectedPerBroadcast) {
msgCount = 0;
broadcast();
}
}
}
} else {
process.on('message', (msg) => {
process.send(msg);
});
}

443
benchmark/common.js Normal file
View File

@ -0,0 +1,443 @@
'use strict';
const child_process = require('child_process');
const http_benchmarkers = require('./_http-benchmarkers.js');
function allow() {
return true;
}
class Benchmark {
constructor(fn, configs, options = {}) {
// Used to make sure a benchmark only start a timer once
this._started = false;
// Indicate that the benchmark ended
this._ended = false;
// Holds process.hrtime value
this._time = 0n;
// Use the file name as the name of the benchmark
this.name = require.main.filename.slice(__dirname.length + 1);
// Execution arguments i.e. flags used to run the jobs
this.flags = process.env.NODE_BENCHMARK_FLAGS?.split(/\s+/) ?? [];
// Parse job-specific configuration from the command line arguments
const argv = process.argv.slice(2);
const parsed_args = this._parseArgs(argv, configs, options);
this.options = parsed_args.cli;
this.extra_options = parsed_args.extra;
this.combinationFilter = typeof options.combinationFilter === 'function' ? options.combinationFilter : allow;
if (options.byGroups) {
this.queue = [];
const groupNames = process.env.NODE_RUN_BENCHMARK_GROUPS?.split(',') ?? Object.keys(configs);
for (const groupName of groupNames) {
const config = { ...configs[groupName][0], group: groupName };
const parsed_args = this._parseArgs(argv, config, options);
this.options = parsed_args.cli;
this.extra_options = parsed_args.extra;
this.queue = this.queue.concat(this._queue(this.options));
}
} else {
this.queue = this._queue(this.options);
}
if (options.flags) {
this.flags = this.flags.concat(options.flags);
}
if (this.queue.length === 0)
return;
// The configuration of the current job, head of the queue
this.config = this.queue[0];
process.nextTick(() => {
if (process.env.NODE_RUN_BENCHMARK_FN !== undefined) {
fn(this.config);
} else {
// _run will use fork() to create a new process for each configuration
// combination.
this._run();
}
});
}
_parseArgs(argv, configs, options) {
const cliOptions = {};
// Check for the test mode first.
const testIndex = argv.indexOf('--test');
if (testIndex !== -1) {
for (const [key, rawValue] of Object.entries(configs)) {
let value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
// Set numbers to one by default to reduce the runtime.
if (typeof value === 'number') {
if (key === 'dur' || key === 'duration') {
value = 0.05;
} else if (value > 1) {
value = 1;
}
}
cliOptions[key] = [value];
}
// Override specific test options.
if (options.test) {
for (const [key, value] of Object.entries(options.test)) {
cliOptions[key] = Array.isArray(value) ? value : [value];
}
}
argv.splice(testIndex, 1);
} else {
// Accept single values instead of arrays.
for (const [key, value] of Object.entries(configs)) {
if (!Array.isArray(value))
configs[key] = [value];
}
}
const extraOptions = {};
const validArgRE = /^(.+?)=([\s\S]*)$/;
// Parse configuration arguments
for (const arg of argv) {
const match = arg.match(validArgRE);
if (!match) {
console.error(`bad argument: ${arg}`);
process.exit(1);
}
const [, key, value] = match;
if (configs[key] !== undefined) {
cliOptions[key] ||= [];
cliOptions[key].push(
// Infer the type from the config object and parse accordingly
typeof configs[key][0] === 'number' ? +value : value,
);
} else {
extraOptions[key] = value;
}
}
return { cli: { ...configs, ...cliOptions }, extra: extraOptions };
}
_queue(options) {
const queue = [];
const keys = Object.keys(options);
const { combinationFilter } = this;
// Perform a depth-first walk through all options to generate a
// configuration list that contains all combinations.
function recursive(keyIndex, prevConfig) {
const key = keys[keyIndex];
const values = options[key];
for (const value of values) {
if (typeof value !== 'number' && typeof value !== 'string') {
throw new TypeError(
`configuration "${key}" had type ${typeof value}`);
}
if (typeof value !== typeof values[0]) {
// This is a requirement for being able to consistently and
// predictably parse CLI provided configuration values.
throw new TypeError(`configuration "${key}" has mixed types`);
}
const currConfig = { [key]: value, ...prevConfig };
if (keyIndex + 1 < keys.length) {
recursive(keyIndex + 1, currConfig);
} else {
// Check if we should allow the current combination
const allowed = combinationFilter({ ...currConfig });
if (typeof allowed !== 'boolean') {
throw new TypeError(
'Combination filter must always return a boolean',
);
}
if (allowed)
queue.push(currConfig);
}
}
}
if (keys.length > 0) {
recursive(0, {});
} else {
queue.push({});
}
return queue;
}
http(options, cb) {
const http_options = { ...options };
http_options.benchmarker ||= this.config.benchmarker ||
this.extra_options.benchmarker ||
http_benchmarkers.default_http_benchmarker;
http_benchmarkers.run(
http_options, (error, code, used_benchmarker, result, elapsed) => {
if (cb) {
cb(code);
}
if (error) {
console.error(error);
process.exit(code || 1);
}
this.config.benchmarker = used_benchmarker;
this.report(result, elapsed);
},
);
}
_run() {
// If forked, report to the parent.
if (process.send) {
process.send({
type: 'config',
name: this.name,
queueLength: this.queue.length,
});
}
const recursive = (queueIndex) => {
const config = this.queue[queueIndex];
// Set NODE_RUN_BENCHMARK_FN to indicate that the child shouldn't
// construct a configuration queue, but just execute the benchmark
// function.
const childEnv = { ...process.env };
childEnv.NODE_RUN_BENCHMARK_FN = '';
// Create configuration arguments
const childArgs = [];
for (const [key, value] of Object.entries(config)) {
childArgs.push(`${key}=${value}`);
}
for (const [key, value] of Object.entries(this.extra_options)) {
childArgs.push(`${key}=${value}`);
}
const child = child_process.fork(require.main.filename, childArgs, {
env: childEnv,
execArgv: this.flags.concat(process.execArgv),
});
child.on('message', sendResult);
child.on('close', (code) => {
if (code) {
process.exit(code);
}
if (queueIndex + 1 < this.queue.length) {
recursive(queueIndex + 1);
}
});
};
recursive(0);
}
start() {
if (this._started) {
throw new Error('Called start more than once in a single benchmark');
}
this._started = true;
this._time = process.hrtime.bigint();
}
end(operations) {
// Get elapsed time now and do error checking later for accuracy.
const time = process.hrtime.bigint();
if (!this._started) {
throw new Error('called end without start');
}
if (this._ended) {
throw new Error('called end multiple times');
}
if (typeof operations !== 'number') {
throw new Error('called end() without specifying operation count');
}
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED && operations <= 0) {
throw new Error('called end() with operation count <= 0');
}
this._ended = true;
if (time === this._time) {
if (!process.env.NODEJS_BENCHMARK_ZERO_ALLOWED)
throw new Error('insufficient clock precision for short benchmark');
// Avoid dividing by zero
this.report(operations && Number.MAX_VALUE, 0n);
return;
}
const elapsed = time - this._time;
const rate = operations / (Number(elapsed) / 1e9);
this.report(rate, elapsed);
}
report(rate, elapsed) {
sendResult({
name: this.name,
conf: this.config,
rate,
time: nanoSecondsToString(elapsed),
type: 'report',
});
}
}
function nanoSecondsToString(bigint) {
const str = bigint.toString();
const decimalPointIndex = str.length - 9;
if (decimalPointIndex <= 0) {
return `0.${'0'.repeat(-decimalPointIndex)}${str}`;
}
return `${str.slice(0, decimalPointIndex)}.${str.slice(decimalPointIndex)}`;
}
function formatResult(data) {
// Construct configuration string, " A=a, B=b, ..."
let conf = '';
for (const key of Object.keys(data.conf)) {
conf += ` ${key}=${JSON.stringify(data.conf[key])}`;
}
let rate = data.rate.toString().split('.');
rate[0] = rate[0].replace(/(\d)(?=(?:\d\d\d)+(?!\d))/g, '$1,');
rate = (rate[1] ? rate.join('.') : rate[0]);
return `${data.name}${conf}: ${rate}\n`;
}
function sendResult(data) {
if (process.send) {
// If forked, report by process send
process.send(data, () => {
if (process.env.NODE_RUN_BENCHMARK_FN !== undefined) {
// If, for any reason, the process is unable to self close within
// a second after completing, forcefully close it.
require('timers').setTimeout(() => {
process.exit(0);
}, 5000).unref();
}
});
} else {
// Otherwise report by stdout
process.stdout.write(formatResult(data));
}
}
const urls = {
long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
'/5d49/b3020/url.html#test?payload1=true&payload2=false&test=1' +
'&benchmark=3&foo=38.38.011.293&bar=1234834910480&test=19299&3992&' +
'key=f5c65e1e98fe07e648249ad41e1cfdb0',
short: 'https://nodejs.org/en/blog/',
idn: 'http://你好你好.在线',
auth: 'https://user:pass@example.com/path?search=1',
file: 'file:///foo/bar/test/node.js',
ws: 'ws://localhost:9229/f46db715-70df-43ad-a359-7f9949f39868',
javascript: 'javascript:alert("node is awesome");',
percent: 'https://%E4%BD%A0/foo',
dot: 'https://example.org/./a/../b/./c',
};
const searchParams = {
noencode: 'foo=bar&baz=quux&xyzzy=thud',
multicharsep: 'foo=bar&&&&&&&&&&baz=quux&&&&&&&&&&xyzzy=thud',
encodefake: 'foo=%©ar&baz=%A©uux&xyzzy=%©ud',
encodemany: '%66%6F%6F=bar&%62%61%7A=quux&xyzzy=%74h%75d',
encodelast: 'foo=bar&baz=quux&xyzzy=thu%64',
multivalue: 'foo=bar&foo=baz&foo=quux&quuy=quuz',
multivaluemany: 'foo=bar&foo=baz&foo=quux&quuy=quuz&foo=abc&foo=def&' +
'foo=ghi&foo=jkl&foo=mno&foo=pqr&foo=stu&foo=vwxyz',
manypairs: 'a&b&c&d&e&f&g&h&i&j&k&l&m&n&o&p&q&r&s&t&u&v&w&x&y&z',
manyblankpairs: '&&&&&&&&&&&&&&&&&&&&&&&&',
altspaces: 'foo+bar=baz+quux&xyzzy+thud=quuy+quuz&abc=def+ghi',
};
function getUrlData(withBase) {
const data = require('../test/fixtures/wpt/url/resources/urltestdata.json');
const result = [];
for (const item of data) {
if (item.failure || !item.input) continue;
if (withBase) {
// item.base might be null. It should be converted into `undefined`.
result.push([item.input, item.base ?? undefined]);
} else if (item.base !== null) {
result.push(item.base);
}
}
return result;
}
/**
* Generate an array of data for URL benchmarks to use.
* The size of the resulting data set is the original data size * 2 ** `e`.
* The 'wpt' type contains about 400 data points when `withBase` is true,
* and 200 data points when `withBase` is false.
* Other types contain 200 data points with or without base.
* @param {string} type Type of the data, 'wpt' or a key of `urls`
* @param {number} e The repetition of the data, as exponent of 2
* @param {boolean} withBase Whether to include a base URL
* @param {boolean} asUrl Whether to return the results as URL objects
* @return {string[] | string[][] | URL[]}
*/
function bakeUrlData(type, e = 0, withBase = false, asUrl = false) {
let result = [];
if (type === 'wpt') {
result = getUrlData(withBase);
} else if (urls[type]) {
const input = urls[type];
const item = withBase ? [input, 'about:blank'] : input;
// Roughly the size of WPT URL test data
result = new Array(200).fill(item);
} else {
throw new Error(`Unknown url data type ${type}`);
}
if (typeof e !== 'number') {
throw new Error(`e must be a number, received ${e}`);
}
for (let i = 0; i < e; ++i) {
result = result.concat(result);
}
if (asUrl) {
if (withBase) {
result = result.map(([input, base]) => new URL(input, base));
} else {
result = result.map((input) => new URL(input));
}
}
return result;
}
module.exports = {
Benchmark,
PORT: http_benchmarkers.PORT,
bakeUrlData,
binding(bindingName) {
try {
const { internalBinding } = require('internal/test/binding');
return internalBinding(bindingName);
} catch {
return process.binding(bindingName);
}
},
buildType: process.features.debug ? 'Debug' : 'Release',
createBenchmark(fn, configs, options) {
return new Benchmark(fn, configs, options);
},
sendResult,
searchParams,
urlDataTypes: Object.keys(urls).concat(['wpt']),
urls,
};

120
benchmark/compare.R Normal file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env Rscript
library(ggplot2);
library(plyr);
# get __dirname and load ./_cli.R
args = commandArgs(trailingOnly = F);
dirname = dirname(sub("--file=", "", args[grep("--file", args)]));
source(paste0(dirname, '/_cli.R'), chdir=T);
if (!is.null(args.options$help) ||
(!is.null(args.options$plot) && args.options$plot == TRUE)) {
stop("usage: cat file.csv | Rscript compare.R
--help show this message
--plot filename save plot to filename");
}
plot.filename = args.options$plot;
dat = read.csv(
file('stdin'),
colClasses=c('character', 'character', 'character', 'numeric', 'numeric')
);
dat = data.frame(dat);
dat$nameTwoLines = paste0(dat$filename, '\n', dat$configuration);
dat$name = paste0(dat$filename, ' ', dat$configuration);
# Create a box plot
if (!is.null(plot.filename)) {
p = ggplot(data=dat);
p = p + geom_boxplot(aes(x=nameTwoLines, y=rate, fill=binary));
p = p + ylab("rate of operations (higher is better)");
p = p + xlab("benchmark");
p = p + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5));
ggsave(plot.filename, p);
}
# Computes the shared standard error, as used in Welch's t-test.
welch.sd = function (old.rate, new.rate) {
old.se.squared = var(old.rate) / length(old.rate)
new.se.squared = var(new.rate) / length(new.rate)
return(sqrt(old.se.squared + new.se.squared))
}
# Calculate the improvement confidence interval. The improvement is calculated
# by dividing by old.mu and not new.mu, because old.mu is what the mean
# improvement is calculated relative to.
confidence.interval = function (shared.se, old.mu, w, risk) {
interval = qt(1 - (risk / 2), w$parameter) * shared.se;
return(sprintf("±%.2f%%", (interval / old.mu) * 100))
}
# Calculate the statistics table.
statistics = ddply(dat, "name", function(subdat) {
old.rate = subset(subdat, binary == "old")$rate;
new.rate = subset(subdat, binary == "new")$rate;
# Calculate improvement for the "new" binary compared with the "old" binary
old.mu = mean(old.rate);
new.mu = mean(new.rate);
improvement = sprintf("%.2f %%", ((new.mu - old.mu) / old.mu * 100));
r = list(
confidence = "NA",
improvement = improvement,
"accuracy (*)" = "NA",
"(**)" = "NA",
"(***)" = "NA"
);
# Check if there is enough data to calculate the p-value.
if (length(old.rate) > 1 && length(new.rate) > 1) {
# Perform a statistical test to see if there actually is a difference in
# performance.
w = t.test(rate ~ binary, data=subdat);
shared.se = welch.sd(old.rate, new.rate)
# Add user-friendly stars to the table. There should be at least one star
# before you can say that there is an improvement.
confidence = '';
if (w$p.value < 0.001) {
confidence = '***';
} else if (w$p.value < 0.01) {
confidence = '**';
} else if (w$p.value < 0.05) {
confidence = '*';
}
r = list(
confidence = confidence,
improvement = improvement,
"accuracy (*)" = confidence.interval(shared.se, old.mu, w, 0.05),
"(**)" = confidence.interval(shared.se, old.mu, w, 0.01),
"(***)" = confidence.interval(shared.se, old.mu, w, 0.001)
);
}
return(data.frame(r, check.names=FALSE));
});
# Set the benchmark names as the row.names to left align them in the print.
row.names(statistics) = statistics$name;
statistics$name = NULL;
options(width = 200);
print(statistics);
cat("\n")
cat(sprintf(
"Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are %d comparisons, you can thus
expect the following amount of false-positive results:
%.2f false positives, when considering a 5%% risk acceptance (*, **, ***),
%.2f false positives, when considering a 1%% risk acceptance (**, ***),
%.2f false positives, when considering a 0.1%% risk acceptance (***)
",
nrow(statistics),
nrow(statistics) * 0.05,
nrow(statistics) * 0.01,
nrow(statistics) * 0.001))

130
benchmark/compare.js Normal file
View File

@ -0,0 +1,130 @@
'use strict';
const { spawn, fork } = require('node:child_process');
const { inspect } = require('util');
const path = require('path');
const CLI = require('./_cli.js');
const BenchmarkProgress = require('./_benchmark_progress.js');
//
// Parse arguments
//
const cli = new CLI(`usage: ./node compare.js [options] [--] <category> ...
Run each benchmark in the <category> directory many times using two different
node versions. More than one <category> directory can be specified.
The output is formatted as csv, which can be processed using for
example 'compare.R'.
--new ./new-node-binary new node binary (required)
--old ./old-node-binary old node binary (required)
--runs 30 number of samples
--filter pattern includes only benchmark scripts matching
<pattern> (can be repeated)
--exclude pattern excludes scripts matching <pattern> (can be
repeated)
--set variable=value set benchmark variable (can be repeated)
--no-progress don't show benchmark progress indicator
Examples:
--set CPUSET=0 Runs benchmarks on CPU core 0.
--set CPUSET=0-2 Specifies that benchmarks should run on CPU cores 0 to 2.
Note: The CPUSET format should match the specifications of the 'taskset' command
`, { arrayArgs: ['set', 'filter', 'exclude'], boolArgs: ['no-progress'] });
if (!cli.optional.new || !cli.optional.old) {
cli.abort(cli.usage);
}
const binaries = ['old', 'new'];
const runs = cli.optional.runs ? parseInt(cli.optional.runs, 10) : 30;
const benchmarks = cli.benchmarks();
if (benchmarks.length === 0) {
console.error('No benchmarks found');
process.exitCode = 1;
return;
}
// Create queue from the benchmarks list such both node versions are tested
// `runs` amount of times each.
// Note: BenchmarkProgress relies on this order to estimate
// how much runs remaining for a file. All benchmarks generated from
// the same file must be run consecutively.
const queue = [];
for (const filename of benchmarks) {
for (let iter = 0; iter < runs; iter++) {
for (const binary of binaries) {
queue.push({ binary, filename, iter });
}
}
}
// queue.length = binary.length * runs * benchmarks.length
// Print csv header
console.log('"binary","filename","configuration","rate","time"');
const kStartOfQueue = 0;
const showProgress = !cli.optional['no-progress'];
let progress;
if (showProgress) {
progress = new BenchmarkProgress(queue, benchmarks);
progress.startQueue(kStartOfQueue);
}
(function recursive(i) {
const job = queue[i];
const resolvedPath = path.resolve(__dirname, job.filename);
const cpuCore = cli.getCpuCoreSetting();
let child;
if (cpuCore !== null) {
const spawnArgs = ['-c', cpuCore, cli.optional[job.binary], resolvedPath, ...cli.optional.set];
child = spawn('taskset', spawnArgs, {
env: process.env,
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
});
} else {
child = fork(resolvedPath, cli.optional.set, {
execPath: cli.optional[job.binary],
});
}
child.on('message', (data) => {
if (data.type === 'report') {
// Construct configuration string, " A=a, B=b, ..."
let conf = '';
for (const key of Object.keys(data.conf)) {
conf += ` ${key}=${inspect(data.conf[key])}`;
}
conf = conf.slice(1);
// Escape quotes (") for correct csv formatting
conf = conf.replace(/"/g, '""');
console.log(`"${job.binary}","${job.filename}","${conf}",` +
`${data.rate},${data.time}`);
if (showProgress) {
// One item in the subqueue has been completed.
progress.completeConfig(data);
}
} else if (showProgress && data.type === 'config') {
// The child has computed the configurations, ready to run subqueue.
progress.startSubqueue(data, i);
}
});
child.once('close', (code) => {
if (code) {
process.exit(code);
}
if (showProgress) {
progress.completeRun(job);
}
// If there are more benchmarks execute the next
if (i + 1 < queue.length) {
recursive(i + 1);
}
});
})(kStartOfQueue);

24
benchmark/cpu.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
CPUPATH=/sys/devices/system/cpu
MAXID=$(cat $CPUPATH/present | awk -F- '{print $NF}')
set_governor() {
echo "Setting CPU frequency governor to \"$1\""
i=0
while [ "$i" -le "$MAXID" ]; do
echo "$1" > "$CPUPATH/cpu$i/cpufreq/scaling_governor"
i=$((i + 1))
done
}
case "$1" in
fast | performance)
set_governor "performance"
;;
*)
echo "Usage: $0 fast"
exit 1
;;
esac

View File

@ -0,0 +1,39 @@
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const keylen = { 'aes-128-gcm': 16, 'aes-192-gcm': 24, 'aes-256-gcm': 32 };
const bench = common.createBenchmark(main, {
n: [2500],
cipher: ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'],
len: [1024, 4 * 1024, 16 * 1024, 64 * 1024, 256 * 1024, 1024 * 1024],
});
function main({ n, len, cipher }) {
const message = Buffer.alloc(len, 'b');
const key = crypto.randomBytes(keylen[cipher]);
const iv = crypto.randomBytes(12);
const associate_data = Buffer.alloc(16, 'z');
bench.start();
AEAD_Bench(cipher, message, associate_data, key, iv, n, len);
}
function AEAD_Bench(cipher, message, associate_data, key, iv, n, len) {
const written = n * len;
const bits = written * 8;
const mbits = bits / (1024 * 1024);
for (let i = 0; i < n; i++) {
const alice = crypto.createCipheriv(cipher, key, iv);
alice.setAAD(associate_data);
const enc = alice.update(message);
alice.final();
const tag = alice.getAuthTag();
const bob = crypto.createDecipheriv(cipher, key, iv);
bob.setAuthTag(tag);
bob.setAAD(associate_data);
bob.update(enc);
bob.final();
}
bench.end(mbits);
}

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common.js');
const { createHash } = require('crypto');
const assert = require('assert');
const bench = common.createBenchmark(main, {
n: [1e5],
});
function main({ n }) {
const array = [];
for (let i = 0; i < n; ++i) {
array.push(null);
}
bench.start();
for (let i = 0; i < n; ++i) {
array[i] = createHash('sha1');
}
bench.end(n);
assert.strictEqual(typeof array[n - 1], 'object');
}

View File

@ -0,0 +1,85 @@
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/');
function readKey(name) {
return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8');
}
function readKeyPair(publicKeyName, privateKeyName) {
return {
publicKey: readKey(publicKeyName),
privateKey: readKey(privateKeyName),
};
}
const keyFixtures = {
ec: readKeyPair('ec_p256_public', 'ec_p256_private'),
rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'),
ed25519: readKeyPair('ed25519_public', 'ed25519_private'),
};
const bench = common.createBenchmark(main, {
keyType: ['rsa', 'ec', 'ed25519'],
keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private'],
n: [1e3],
});
function measure(n, fn, input) {
bench.start();
for (let i = 0; i < n; ++i) {
fn(input);
}
bench.end(n);
}
function main({ n, keyFormat, keyType }) {
const keyPair = {
publicKey: crypto.createPublicKey(keyFixtures[keyType].publicKey),
privateKey: crypto.createPrivateKey(keyFixtures[keyType].privateKey),
};
let key, fn;
switch (keyFormat) {
case 'spki':
key = keyPair.publicKey.export({ format: 'pem', type: 'spki' });
fn = crypto.createPublicKey;
break;
case 'pkcs8':
key = keyPair.privateKey.export({ format: 'pem', type: 'pkcs8' });
fn = crypto.createPrivateKey;
break;
case 'der-spki': {
const options = { format: 'der', type: 'spki' };
key = { ...options, key: keyPair.publicKey.export(options) };
fn = crypto.createPublicKey;
break;
}
case 'der-pkcs8': {
const options = { format: 'der', type: 'pkcs8' };
key = { ...options, key: keyPair.privateKey.export(options) };
fn = crypto.createPrivateKey;
break;
}
case 'jwk-public': {
const options = { format: 'jwk' };
key = { ...options, key: keyPair.publicKey.export(options) };
fn = crypto.createPublicKey;
break;
}
case 'jwk-private': {
const options = { format: 'jwk' };
key = { ...options, key: keyPair.privateKey.export(options) };
fn = crypto.createPrivateKey;
break;
}
default:
throw new Error('not implemented');
}
measure(n, fn, key);
}

View File

@ -0,0 +1,21 @@
'use strict';
const common = require('../common.js');
const bench = common.createBenchmark(main, {
n: [1, 500000],
v: ['crypto', 'tls'],
});
function main({ n, v }) {
const method = require(v).getCiphers;
let i = 0;
// First call to getCiphers will dominate the results
if (n > 1) {
for (; i < n; i++)
method();
}
bench.start();
for (i = 0; i < n; i++) method();
bench.end(n);
}

View File

@ -0,0 +1,78 @@
// Throughput benchmark
// creates a single hasher, then pushes a bunch of data through it
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const bench = common.createBenchmark(main, {
writes: [500],
algo: [ 'sha256', 'md5' ],
type: ['asc', 'utf', 'buf'],
out: ['hex', 'binary', 'buffer'],
len: [2, 1024, 102400, 1024 * 1024],
api: ['legacy', 'stream'],
});
function main({ api, type, len, out, writes, algo }) {
if (api === 'stream' && /^v0\.[0-8]\./.test(process.version)) {
console.error('Crypto streams not available until v0.10');
// Use the legacy, just so that we can compare them.
api = 'legacy';
}
let message;
let encoding;
switch (type) {
case 'asc':
message = 'a'.repeat(len);
encoding = 'ascii';
break;
case 'utf':
message = 'ü'.repeat(len / 2);
encoding = 'utf8';
break;
case 'buf':
message = Buffer.alloc(len, 'b');
break;
default:
throw new Error(`unknown message type: ${type}`);
}
const fn = api === 'stream' ? streamWrite : legacyWrite;
bench.start();
fn(algo, message, encoding, writes, len, out);
}
function legacyWrite(algo, message, encoding, writes, len, outEnc) {
const written = writes * len;
const bits = written * 8;
const gbits = bits / (1024 * 1024 * 1024);
while (writes-- > 0) {
const h = crypto.createHash(algo);
h.update(message, encoding);
h.digest(outEnc);
}
bench.end(gbits);
}
function streamWrite(algo, message, encoding, writes, len, outEnc) {
const written = writes * len;
const bits = written * 8;
const gbits = bits / (1024 * 1024 * 1024);
while (writes-- > 0) {
const h = crypto.createHash(algo);
if (outEnc !== 'buffer')
h.setEncoding(outEnc);
h.write(message, encoding);
h.end();
h.read();
}
bench.end(gbits);
}

View File

@ -0,0 +1,73 @@
// Throughput benchmark
// creates a single hasher, then pushes a bunch of data through it
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const bench = common.createBenchmark(main, {
writes: [500],
algo: ['sha1', 'sha256', 'sha512'],
type: ['asc', 'utf', 'buf'],
len: [2, 1024, 102400, 1024 * 1024],
api: ['legacy', 'stream'],
});
function main({ api, type, len, algo, writes }) {
if (api === 'stream' && /^v0\.[0-8]\./.test(process.version)) {
console.error('Crypto streams not available until v0.10');
// Use the legacy, just so that we can compare them.
api = 'legacy';
}
let message;
let encoding;
switch (type) {
case 'asc':
message = 'a'.repeat(len);
encoding = 'ascii';
break;
case 'utf':
message = 'ü'.repeat(len / 2);
encoding = 'utf8';
break;
case 'buf':
message = Buffer.alloc(len, 'b');
break;
default:
throw new Error(`unknown message type: ${type}`);
}
const fn = api === 'stream' ? streamWrite : legacyWrite;
bench.start();
fn(algo, message, encoding, writes, len);
}
function legacyWrite(algo, message, encoding, writes, len) {
const written = writes * len;
const bits = written * 8;
const gbits = bits / (1024 * 1024 * 1024);
const h = crypto.createHash(algo);
while (writes-- > 0)
h.update(message, encoding);
h.digest();
bench.end(gbits);
}
function streamWrite(algo, message, encoding, writes, len) {
const written = writes * len;
const bits = written * 8;
const gbits = bits / (1024 * 1024 * 1024);
const h = crypto.createHash(algo);
while (writes-- > 0)
h.write(message, encoding);
h.end();
h.read();
bench.end(gbits);
}

44
benchmark/crypto/hkdf.js Normal file
View File

@ -0,0 +1,44 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const {
hkdf,
hkdfSync,
} = require('crypto');
const bench = common.createBenchmark(main, {
sync: [0, 1],
size: [10, 64, 1024],
key: ['a', 'secret', 'this-is-a-much-longer-secret'],
salt: ['', 'salt'],
info: ['', 'info'],
hash: ['sha256', 'sha512'],
n: [1e4],
});
function measureSync(n, size, salt, info, hash, key) {
bench.start();
for (let i = 0; i < n; ++i)
hkdfSync(hash, key, salt, info, size);
bench.end(n);
}
function measureAsync(n, size, salt, info, hash, key) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
hkdf(hash, key, salt, info, size, done);
}
function main({ n, sync, size, salt, info, hash, key }) {
if (sync)
measureSync(n, size, salt, info, hash, key);
else
measureAsync(n, size, salt, info, hash, key);
}

View File

@ -0,0 +1,71 @@
'use strict';
const common = require('../common.js');
const assert = require('assert');
const {
generateKeyPair,
generateKeyPairSync,
} = require('crypto');
const bench = common.createBenchmark(main, {
method: ['rsaSync', 'rsaAsync', 'dsaSync', 'dsaAsync'],
n: [1e2],
});
const methods = {
rsaSync(n) {
bench.start();
for (let i = 0; i < n; ++i) {
generateKeyPairSync('rsa', {
modulusLength: 1024,
publicExponent: 0x10001,
});
}
bench.end(n);
},
rsaAsync(n) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
generateKeyPair('rsa', {
modulusLength: 512,
publicExponent: 0x10001,
}, done);
},
dsaSync(n) {
bench.start();
for (let i = 0; i < n; ++i) {
generateKeyPairSync('dsa', {
modulusLength: 1024,
divisorLength: 160,
});
}
bench.end(n);
},
dsaAsync(n) {
let remaining = n;
function done(err) {
assert.ifError(err);
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i)
generateKeyPair('dsa', {
modulusLength: 1024,
divisorLength: 160,
}, done);
},
};
function main({ n, method }) {
methods[method](n);
}

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common.js');
const { createHash, hash } = require('crypto');
const path = require('path');
const filepath = path.resolve(__dirname, '../../test/fixtures/snapshot/typescript.js');
const fs = require('fs');
const assert = require('assert');
const bench = common.createBenchmark(main, {
length: [1000, 100_000],
method: ['md5', 'sha1', 'sha256'],
type: ['string', 'buffer'],
n: [100_000, 1000],
}, {
combinationFilter: ({ length, n }) => {
return length * n <= 100_000 * 1000;
},
});
function main({ length, type, method, n }) {
let data = fs.readFileSync(filepath);
if (type === 'string') {
data = data.toString().slice(0, length);
} else {
data = Uint8Array.prototype.slice.call(data, 0, length);
}
const oneshotHash = hash ?
(method, input) => hash(method, input, 'hex') :
(method, input) => createHash(method).update(input).digest('hex');
const array = [];
for (let i = 0; i < n; i++) {
array.push(null);
}
bench.start();
for (let i = 0; i < n; i++) {
array[i] = oneshotHash(method, data);
}
bench.end(n);
assert.strictEqual(typeof array[n - 1], 'string');
}

View File

@ -0,0 +1,131 @@
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/');
const keyFixtures = {
ec: fs.readFileSync(`${fixtures_keydir}/ec_p256_private.pem`, 'utf-8'),
rsa: fs.readFileSync(`${fixtures_keydir}/rsa_private_2048.pem`, 'utf-8'),
ed25519: fs.readFileSync(`${fixtures_keydir}/ed25519_private.pem`, 'utf-8'),
};
const data = crypto.randomBytes(256);
let pems;
let keyObjects;
const bench = common.createBenchmark(main, {
keyType: ['rsa', 'ec', 'ed25519'],
mode: ['sync', 'async', 'async-parallel'],
keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'],
n: [1e3],
}, {
combinationFilter(p) {
// "keyObject.unique" allows to compare the result with "keyObject" to
// assess whether mutexes over the key material impact the operation
return p.keyFormat !== 'keyObject.unique' ||
(p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel');
},
});
function measureSync(n, digest, privateKey, keys) {
bench.start();
for (let i = 0; i < n; ++i) {
crypto.sign(
digest,
data,
privateKey || keys[i]);
}
bench.end(n);
}
function measureAsync(n, digest, privateKey, keys) {
let remaining = n;
function done() {
if (--remaining === 0)
bench.end(n);
else
one();
}
function one() {
crypto.sign(
digest,
data,
privateKey || keys[n - remaining],
done);
}
bench.start();
one();
}
function measureAsyncParallel(n, digest, privateKey, keys) {
let remaining = n;
function done() {
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i) {
crypto.sign(
digest,
data,
privateKey || keys[i],
done);
}
}
function main({ n, mode, keyFormat, keyType }) {
pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType]);
keyObjects ||= pems.map(crypto.createPrivateKey);
let privateKey, keys, digest;
switch (keyType) {
case 'rsa':
case 'ec':
digest = 'sha256';
break;
case 'ed25519':
break;
default:
throw new Error('not implemented');
}
switch (keyFormat) {
case 'keyObject':
privateKey = keyObjects[0];
break;
case 'pem':
privateKey = pems[0];
break;
case 'jwk': {
privateKey = { key: keyObjects[0].export({ format: 'jwk' }), format: 'jwk' };
break;
}
case 'der': {
privateKey = { key: keyObjects[0].export({ format: 'der', type: 'pkcs8' }), format: 'der', type: 'pkcs8' };
break;
}
case 'keyObject.unique':
keys = keyObjects;
break;
default:
throw new Error('not implemented');
}
switch (mode) {
case 'sync':
measureSync(n, digest, privateKey, keys);
break;
case 'async':
measureAsync(n, digest, privateKey, keys);
break;
case 'async-parallel':
measureAsyncParallel(n, digest, privateKey, keys);
break;
}
}

View File

@ -0,0 +1,151 @@
'use strict';
const common = require('../common.js');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/');
function readKey(name) {
return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8');
}
function readKeyPair(publicKeyName, privateKeyName) {
return {
publicKey: readKey(publicKeyName),
privateKey: readKey(privateKeyName),
};
}
const keyFixtures = {
ec: readKeyPair('ec_p256_public', 'ec_p256_private'),
rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'),
ed25519: readKeyPair('ed25519_public', 'ed25519_private'),
};
const data = crypto.randomBytes(256);
let pems;
let keyObjects;
const bench = common.createBenchmark(main, {
keyType: ['rsa', 'ec', 'ed25519'],
mode: ['sync', 'async', 'async-parallel'],
keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'],
n: [1e3],
}, {
combinationFilter(p) {
// "keyObject.unique" allows to compare the result with "keyObject" to
// assess whether mutexes over the key material impact the operation
return p.keyFormat !== 'keyObject.unique' ||
(p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel');
},
});
function measureSync(n, digest, signature, publicKey, keys) {
bench.start();
for (let i = 0; i < n; ++i) {
crypto.verify(
digest,
data,
publicKey || keys[i],
signature);
}
bench.end(n);
}
function measureAsync(n, digest, signature, publicKey, keys) {
let remaining = n;
function done() {
if (--remaining === 0)
bench.end(n);
else
one();
}
function one() {
crypto.verify(
digest,
data,
publicKey || keys[n - remaining],
signature,
done);
}
bench.start();
one();
}
function measureAsyncParallel(n, digest, signature, publicKey, keys) {
let remaining = n;
function done() {
if (--remaining === 0)
bench.end(n);
}
bench.start();
for (let i = 0; i < n; ++i) {
crypto.verify(
digest,
data,
publicKey || keys[i],
signature,
done);
}
}
function main({ n, mode, keyFormat, keyType }) {
pems ||= [...Buffer.alloc(n)].map(() => keyFixtures[keyType].publicKey);
keyObjects ||= pems.map(crypto.createPublicKey);
let publicKey, keys, digest;
switch (keyType) {
case 'rsa':
case 'ec':
digest = 'sha256';
break;
case 'ed25519':
break;
default:
throw new Error('not implemented');
}
switch (keyFormat) {
case 'keyObject':
publicKey = keyObjects[0];
break;
case 'pem':
publicKey = pems[0];
break;
case 'jwk': {
publicKey = { key: keyObjects[0].export({ format: 'jwk' }), format: 'jwk' };
break;
}
case 'der': {
publicKey = { key: keyObjects[0].export({ format: 'der', type: 'spki' }), format: 'der', type: 'spki' };
break;
}
case 'keyObject.unique':
keys = keyObjects;
break;
default:
throw new Error('not implemented');
}
const { privateKey } = keyFixtures[keyType];
const signature = crypto.sign(digest, data, privateKey);
switch (mode) {
case 'sync':
measureSync(n, digest, signature, publicKey, keys);
break;
case 'async':
measureAsync(n, digest, signature, publicKey, keys);
break;
case 'async-parallel':
measureAsyncParallel(n, digest, signature, publicKey, keys);
break;
default:
throw new Error('not implemented');
}
}

View File

@ -0,0 +1,23 @@
'use strict';
const common = require('../common.js');
const { randomBytes } = require('crypto');
// Add together with imports
const assert = require('assert');
let _cryptoResult;
const bench = common.createBenchmark(main, {
size: [64, 1024, 8 * 1024, 16 * 1024],
n: [1e5],
});
function main({ n, size }) {
bench.start();
for (let i = 0; i < n; ++i)
_cryptoResult = randomBytes(size);
bench.end(n);
// Avoid V8 deadcode (elimination)
assert.ok(_cryptoResult);
}

View File

@ -0,0 +1,38 @@
'use strict';
const common = require('../common.js');
const { randomInt } = require('crypto');
const bench = common.createBenchmark(main, {
mode: ['sync', 'async-sequential', 'async-parallel'],
min: [-(2 ** 47) + 1, -10_000, -100],
max: [100, 10_000, 2 ** 47],
n: [1e3, 1e5],
});
function main({ mode, min, max, n }) {
if (mode === 'sync') {
bench.start();
for (let i = 0; i < n; i++)
randomInt(min, max);
bench.end(n);
} else if (mode === 'async-sequential') {
bench.start();
(function next(i) {
if (i === n)
return bench.end(n);
randomInt(min, max, () => {
next(i + 1);
});
})(0);
} else {
bench.start();
let done = 0;
for (let i = 0; i < n; i++) {
randomInt(min, max, () => {
if (++done === n)
bench.end(n);
});
}
}
}

View File

@ -0,0 +1,17 @@
'use strict';
const common = require('../common.js');
const { randomUUID } = require('crypto');
const bench = common.createBenchmark(main, {
n: [1e7],
disableEntropyCache: [0, 1],
});
function main({ n, disableEntropyCache }) {
disableEntropyCache = !!disableEntropyCache;
bench.start();
for (let i = 0; i < n; ++i)
randomUUID({ disableEntropyCache });
bench.end(n);
}

Some files were not shown because too many files have changed in this diff Show More