diff --git a/benchmark/_cli.js b/benchmark/_cli.js index eb6c4add9799a4..33ba2e0963f2fe 100644 --- a/benchmark/_cli.js +++ b/benchmark/_cli.js @@ -125,3 +125,24 @@ CLI.prototype.shouldSkip = function(scripts) { 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; +}; diff --git a/benchmark/compare.js b/benchmark/compare.js index 503901f607ef02..1efff9e85c072f 100644 --- a/benchmark/compare.js +++ b/benchmark/compare.js @@ -1,6 +1,6 @@ 'use strict'; -const { fork } = require('child_process'); +const { spawn, fork } = require('node:child_process'); const { inspect } = require('util'); const path = require('path'); const CLI = require('./_cli.js'); @@ -24,6 +24,12 @@ const cli = new CLI(`usage: ./node compare.js [options] [--] ... 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) { @@ -69,10 +75,24 @@ if (showProgress) { (function recursive(i) { const job = queue[i]; - - const child = fork(path.resolve(__dirname, job.filename), cli.optional.set, { - execPath: cli.optional[job.binary], - }); + 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', 'pipe', 'pipe'], + }); + + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + } else { + child = fork(resolvedPath, cli.optional.set, { + execPath: cli.optional[job.binary], + }); + } child.on('message', (data) => { if (data.type === 'report') { diff --git a/benchmark/run.js b/benchmark/run.js index 0b63fb930bb000..11f95d8e71f035 100644 --- a/benchmark/run.js +++ b/benchmark/run.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const fork = require('child_process').fork; +const { spawn, fork } = require('node:child_process'); const CLI = require('./_cli.js'); const cli = new CLI(`usage: ./node run.js [options] [--] ... @@ -17,7 +17,14 @@ const cli = new CLI(`usage: ./node run.js [options] [--] ... test only run a single configuration from the options matrix all each benchmark category is run one after the other + + 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 on your system. `, { arrayArgs: ['set', 'filter', 'exclude'] }); + const benchmarks = cli.benchmarks(); if (benchmarks.length === 0) { @@ -40,10 +47,24 @@ if (format === 'csv') { (function recursive(i) { const filename = benchmarks[i]; - const child = fork( - path.resolve(__dirname, filename), - cli.test ? ['--test'] : cli.optional.set, - ); + const scriptPath = path.resolve(__dirname, filename); + + const args = cli.test ? ['--test'] : cli.optional.set; + const cpuCore = cli.getCpuCoreSetting(); + let child; + if (cpuCore !== null) { + child = spawn('taskset', ['-c', cpuCore, 'node', scriptPath, ...args], { + stdio: ['inherit', 'pipe', 'pipe'], + }); + + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + } else { + child = fork( + scriptPath, + args, + ); + } if (format !== 'csv') { console.log(); diff --git a/doc/contributing/writing-and-running-benchmarks.md b/doc/contributing/writing-and-running-benchmarks.md index 2c8d5eb3ed237b..f70ff965a8d7d1 100644 --- a/doc/contributing/writing-and-running-benchmarks.md +++ b/doc/contributing/writing-and-running-benchmarks.md @@ -10,6 +10,7 @@ * [Running benchmarks](#running-benchmarks) * [Running individual benchmarks](#running-individual-benchmarks) * [Running all benchmarks](#running-all-benchmarks) + * [Specifying CPU Cores for Benchmarks with run.js](#specifying-cpu-cores-for-benchmarks-with-runjs) * [Filtering benchmarks](#filtering-benchmarks) * [Comparing Node.js versions](#comparing-nodejs-versions) * [Comparing parameters](#comparing-parameters) @@ -163,6 +164,33 @@ It is possible to execute more groups by adding extra process arguments. node benchmark/run.js assert async_hooks ``` +#### Specifying CPU Cores for Benchmarks with run.js + +When using `run.js` to execute a group of benchmarks, +you can specify on which CPU cores the +benchmarks should execute +by using the `--set CPUSET=value` option. +This controls the CPU core +affinity for the benchmark process, +potentially reducing +interference from other processes and allowing +for performance +testing under specific hardware configurations. + +The `CPUSET` option utilizes the `taskset` command's format +for setting CPU affinity, where `value` can be a single core +number or a range of cores. + +Examples: + +* `node benchmark/run.js --set CPUSET=0` ... runs benchmarks on CPU core 0. +* `node benchmark/run.js --set CPUSET=0-2` ... + specifies that benchmarks should run on CPU cores 0 to 2. + +Note: This option is only applicable when using `run.js`. +Ensure the `taskset` command is available on your system +and the specified `CPUSET` format matches its requirements. + #### Filtering benchmarks `benchmark/run.js` and `benchmark/compare.js` have `--filter pattern` and @@ -288,8 +316,16 @@ module, you can use the `--filter` option:_ --old ./old-node-binary old node binary (required) --runs 30 number of samples --filter pattern string to filter benchmark scripts + --exclude pattern excludes scripts matching (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 ``` For analyzing the benchmark results, use [node-benchmark-compare][] or the R