overtake 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +3 -0
- package/build/executor.d.ts +1 -0
- package/build/executor.js +10 -1
- package/build/index.js +2 -1
- package/build/types.d.ts +1 -0
- package/build/worker.js +13 -1
- package/package.json +1 -1
- package/src/cli.ts +3 -0
- package/src/executor.ts +12 -1
- package/src/index.ts +2 -0
- package/src/types.ts +1 -0
- package/src/worker.ts +13 -0
package/build/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ const { values: opts, positionals: patterns } = parseArgs({
|
|
|
23
23
|
'max-cycles': { type: 'string' },
|
|
24
24
|
'min-cycles': { type: 'string' },
|
|
25
25
|
'no-gc-observer': { type: 'boolean' },
|
|
26
|
+
'pin-cores': { type: 'boolean' },
|
|
26
27
|
progress: { type: 'boolean' },
|
|
27
28
|
'save-baseline': { type: 'string' },
|
|
28
29
|
'compare-baseline': { type: 'string' },
|
|
@@ -49,6 +50,7 @@ Options:
|
|
|
49
50
|
--max-cycles <n> maximum measurement cycles per feed
|
|
50
51
|
--min-cycles <n> minimum measurement cycles per feed
|
|
51
52
|
--no-gc-observer disable GC overlap detection
|
|
53
|
+
--pin-cores pin each worker to a dedicated CPU core (Linux)
|
|
52
54
|
--progress show progress bar
|
|
53
55
|
--save-baseline <file> save results to baseline file
|
|
54
56
|
--compare-baseline <file> compare results against baseline file
|
|
@@ -69,6 +71,7 @@ const executeOptions = {
|
|
|
69
71
|
maxCycles: opts['max-cycles'] ? parseInt(opts['max-cycles']) : undefined,
|
|
70
72
|
minCycles: opts['min-cycles'] ? parseInt(opts['min-cycles']) : undefined,
|
|
71
73
|
gcObserver: !opts['no-gc-observer'],
|
|
74
|
+
pinCores: opts['pin-cores'] ?? false,
|
|
72
75
|
progress: opts.progress ?? false,
|
|
73
76
|
format,
|
|
74
77
|
};
|
package/build/executor.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report>
|
|
|
9
9
|
export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
|
|
10
10
|
workers?: number;
|
|
11
11
|
maxCycles?: number;
|
|
12
|
+
pinCores?: boolean;
|
|
12
13
|
onProgress?: ProgressCallback;
|
|
13
14
|
progressInterval?: number;
|
|
14
15
|
}
|
package/build/executor.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { cpus } from 'node:os';
|
|
4
5
|
import { createReport, computeStats, Report } from './reporter.js';
|
|
5
6
|
import { cmp, assertNoClosure, normalizeFunction } from './utils.js';
|
|
6
7
|
import { Control, CONTROL_SLOTS, COMPLETE_VALUE, } from './types.js';
|
|
7
8
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
8
9
|
export const createExecutor = (options) => {
|
|
9
|
-
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, onProgress, progressInterval = 100 } = options;
|
|
10
|
+
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, pinCores = false, onProgress, progressInterval = 100 } = options;
|
|
10
11
|
const benchmarkUrl = options[BENCHMARK_URL];
|
|
11
12
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
13
|
+
let coreList = null;
|
|
14
|
+
if (pinCores) {
|
|
15
|
+
const count = cpus().length;
|
|
16
|
+
coreList = count > 1 ? Array.from({ length: count - 1 }, (_, i) => i + 1) : [0];
|
|
17
|
+
}
|
|
18
|
+
let nextCoreIdx = 0;
|
|
12
19
|
const pending = [];
|
|
13
20
|
const activeWorkers = new Set();
|
|
14
21
|
let running = 0;
|
|
@@ -50,6 +57,7 @@ export const createExecutor = (options) => {
|
|
|
50
57
|
assertNoClosure(postCode, 'post');
|
|
51
58
|
const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);
|
|
52
59
|
const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);
|
|
60
|
+
const cpuPin = coreList !== null ? coreList[nextCoreIdx++ % coreList.length] : undefined;
|
|
53
61
|
const workerFile = new URL('./worker.js', import.meta.url);
|
|
54
62
|
const workerData = {
|
|
55
63
|
benchmarkUrl: resolvedBenchmarkUrl,
|
|
@@ -59,6 +67,7 @@ export const createExecutor = (options) => {
|
|
|
59
67
|
runCode,
|
|
60
68
|
postCode,
|
|
61
69
|
data,
|
|
70
|
+
cpuPin,
|
|
62
71
|
warmupCycles,
|
|
63
72
|
minCycles,
|
|
64
73
|
absThreshold,
|
package/build/index.js
CHANGED
|
@@ -99,7 +99,7 @@ export class Benchmark {
|
|
|
99
99
|
return new Target(target);
|
|
100
100
|
}
|
|
101
101
|
async execute(options = {}) {
|
|
102
|
-
const { workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, gcObserver = true, reportTypes = DEFAULT_REPORT_TYPES, progress = false, progressInterval = 100, } = options;
|
|
102
|
+
const { workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, gcObserver = true, reportTypes = DEFAULT_REPORT_TYPES, progress = false, progressInterval = 100, pinCores = false, } = options;
|
|
103
103
|
if (this.#executed) {
|
|
104
104
|
throw new Error("Benchmark is executed and can't be reused");
|
|
105
105
|
}
|
|
@@ -134,6 +134,7 @@ export class Benchmark {
|
|
|
134
134
|
relThreshold,
|
|
135
135
|
gcObserver,
|
|
136
136
|
reportTypes,
|
|
137
|
+
pinCores,
|
|
137
138
|
onProgress,
|
|
138
139
|
progressInterval,
|
|
139
140
|
[BENCHMARK_URL]: benchmarkUrl,
|
package/build/types.d.ts
CHANGED
package/build/worker.js
CHANGED
|
@@ -2,12 +2,24 @@ import { workerData } from 'node:worker_threads';
|
|
|
2
2
|
import { SourceTextModule, SyntheticModule } from 'node:vm';
|
|
3
3
|
import { createRequire, register } from 'node:module';
|
|
4
4
|
import { isAbsolute } from 'node:path';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
5
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
8
|
import { benchmark } from './runner.js';
|
|
7
9
|
import {} from './types.js';
|
|
8
10
|
import { resolveHookUrl } from './utils.js';
|
|
9
11
|
register(resolveHookUrl);
|
|
10
|
-
const { benchmarkUrl, setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver = true, durationsSAB, controlSAB, } = workerData;
|
|
12
|
+
const { benchmarkUrl, setupCode, teardownCode, preCode, runCode, postCode, data, cpuPin, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver = true, durationsSAB, controlSAB, } = workerData;
|
|
13
|
+
if (cpuPin !== undefined && process.platform === 'linux') {
|
|
14
|
+
try {
|
|
15
|
+
const status = readFileSync('/proc/thread-self/status', 'utf8');
|
|
16
|
+
const tid = status.match(/^Pid:\t(\d+)/m)?.[1];
|
|
17
|
+
if (tid) {
|
|
18
|
+
execFileSync('taskset', ['-cp', String(cpuPin), tid], { stdio: 'ignore' });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch { }
|
|
22
|
+
}
|
|
11
23
|
const serialize = (code) => (code ? code : 'undefined');
|
|
12
24
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
13
25
|
const benchmarkDirUrl = new URL('.', resolvedBenchmarkUrl).href;
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -39,6 +39,7 @@ const { values: opts, positionals: patterns } = parseArgs({
|
|
|
39
39
|
'max-cycles': { type: 'string' },
|
|
40
40
|
'min-cycles': { type: 'string' },
|
|
41
41
|
'no-gc-observer': { type: 'boolean' },
|
|
42
|
+
'pin-cores': { type: 'boolean' },
|
|
42
43
|
progress: { type: 'boolean' },
|
|
43
44
|
'save-baseline': { type: 'string' },
|
|
44
45
|
'compare-baseline': { type: 'string' },
|
|
@@ -67,6 +68,7 @@ Options:
|
|
|
67
68
|
--max-cycles <n> maximum measurement cycles per feed
|
|
68
69
|
--min-cycles <n> minimum measurement cycles per feed
|
|
69
70
|
--no-gc-observer disable GC overlap detection
|
|
71
|
+
--pin-cores pin each worker to a dedicated CPU core (Linux)
|
|
70
72
|
--progress show progress bar
|
|
71
73
|
--save-baseline <file> save results to baseline file
|
|
72
74
|
--compare-baseline <file> compare results against baseline file
|
|
@@ -89,6 +91,7 @@ const executeOptions = {
|
|
|
89
91
|
maxCycles: opts['max-cycles'] ? parseInt(opts['max-cycles']) : undefined,
|
|
90
92
|
minCycles: opts['min-cycles'] ? parseInt(opts['min-cycles']) : undefined,
|
|
91
93
|
gcObserver: !opts['no-gc-observer'],
|
|
94
|
+
pinCores: opts['pin-cores'] ?? false,
|
|
92
95
|
progress: opts.progress ?? false,
|
|
93
96
|
format,
|
|
94
97
|
};
|
package/src/executor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { cpus } from 'node:os';
|
|
4
5
|
import { createReport, computeStats, Report } from './reporter.js';
|
|
5
6
|
import { cmp, assertNoClosure, normalizeFunction } from './utils.js';
|
|
6
7
|
import {
|
|
@@ -26,6 +27,7 @@ export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report>
|
|
|
26
27
|
export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
|
|
27
28
|
workers?: number;
|
|
28
29
|
maxCycles?: number;
|
|
30
|
+
pinCores?: boolean;
|
|
29
31
|
onProgress?: ProgressCallback;
|
|
30
32
|
progressInterval?: number;
|
|
31
33
|
}
|
|
@@ -38,10 +40,17 @@ export interface Executor<TContext, TInput> {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export const createExecutor = <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>): Executor<TContext, TInput> => {
|
|
41
|
-
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, onProgress, progressInterval = 100 } = options;
|
|
43
|
+
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, pinCores = false, onProgress, progressInterval = 100 } = options;
|
|
42
44
|
const benchmarkUrl = (options as Record<symbol, unknown>)[BENCHMARK_URL];
|
|
43
45
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
44
46
|
|
|
47
|
+
let coreList: number[] | null = null;
|
|
48
|
+
if (pinCores) {
|
|
49
|
+
const count = cpus().length;
|
|
50
|
+
coreList = count > 1 ? Array.from({ length: count - 1 }, (_, i) => i + 1) : [0];
|
|
51
|
+
}
|
|
52
|
+
let nextCoreIdx = 0;
|
|
53
|
+
|
|
45
54
|
const pending: { task: ExecutorRunOptions<TContext, TInput>; resolve: (v: unknown) => void; reject: (e: unknown) => void }[] = [];
|
|
46
55
|
const activeWorkers = new Set<Worker>();
|
|
47
56
|
let running = 0;
|
|
@@ -84,6 +93,7 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>(optio
|
|
|
84
93
|
const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);
|
|
85
94
|
const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);
|
|
86
95
|
|
|
96
|
+
const cpuPin = coreList !== null ? coreList[nextCoreIdx++ % coreList.length] : undefined;
|
|
87
97
|
const workerFile = new URL('./worker.js', import.meta.url);
|
|
88
98
|
const workerData: WorkerOptions = {
|
|
89
99
|
benchmarkUrl: resolvedBenchmarkUrl,
|
|
@@ -93,6 +103,7 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>(optio
|
|
|
93
103
|
runCode,
|
|
94
104
|
postCode,
|
|
95
105
|
data,
|
|
106
|
+
cpuPin,
|
|
96
107
|
|
|
97
108
|
warmupCycles,
|
|
98
109
|
minCycles,
|
package/src/index.ts
CHANGED
|
@@ -162,6 +162,7 @@ export class Benchmark<TInput> {
|
|
|
162
162
|
reportTypes = DEFAULT_REPORT_TYPES as unknown as R,
|
|
163
163
|
progress = false,
|
|
164
164
|
progressInterval = 100,
|
|
165
|
+
pinCores = false,
|
|
165
166
|
} = options;
|
|
166
167
|
if (this.#executed) {
|
|
167
168
|
throw new Error("Benchmark is executed and can't be reused");
|
|
@@ -201,6 +202,7 @@ export class Benchmark<TInput> {
|
|
|
201
202
|
relThreshold,
|
|
202
203
|
gcObserver,
|
|
203
204
|
reportTypes,
|
|
205
|
+
pinCores,
|
|
204
206
|
onProgress,
|
|
205
207
|
progressInterval,
|
|
206
208
|
[BENCHMARK_URL]: benchmarkUrl,
|
package/src/types.ts
CHANGED
package/src/worker.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { workerData } from 'node:worker_threads';
|
|
|
2
2
|
import { SourceTextModule, SyntheticModule } from 'node:vm';
|
|
3
3
|
import { createRequire, register } from 'node:module';
|
|
4
4
|
import { isAbsolute } from 'node:path';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
5
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
8
|
import { benchmark } from './runner.js';
|
|
7
9
|
import { type WorkerOptions } from './types.js';
|
|
@@ -17,6 +19,7 @@ const {
|
|
|
17
19
|
runCode,
|
|
18
20
|
postCode,
|
|
19
21
|
data,
|
|
22
|
+
cpuPin,
|
|
20
23
|
|
|
21
24
|
warmupCycles,
|
|
22
25
|
minCycles,
|
|
@@ -28,6 +31,16 @@ const {
|
|
|
28
31
|
controlSAB,
|
|
29
32
|
}: WorkerOptions = workerData;
|
|
30
33
|
|
|
34
|
+
if (cpuPin !== undefined && process.platform === 'linux') {
|
|
35
|
+
try {
|
|
36
|
+
const status = readFileSync('/proc/thread-self/status', 'utf8');
|
|
37
|
+
const tid = status.match(/^Pid:\t(\d+)/m)?.[1];
|
|
38
|
+
if (tid) {
|
|
39
|
+
execFileSync('taskset', ['-cp', String(cpuPin), tid], { stdio: 'ignore' });
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
const serialize = (code?: string) => (code ? code : 'undefined');
|
|
32
45
|
|
|
33
46
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|