overtake 1.3.2 → 1.4.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/README.md +8 -0
- package/build/executor.cjs +12 -5
- package/build/executor.cjs.map +1 -1
- package/build/executor.d.ts +1 -0
- package/build/executor.js +13 -6
- package/build/executor.js.map +1 -1
- package/build/index.cjs +58 -16
- package/build/index.cjs.map +1 -1
- package/build/index.js +58 -16
- package/build/index.js.map +1 -1
- package/build/reporter.cjs +63 -116
- package/build/reporter.cjs.map +1 -1
- package/build/reporter.js +65 -118
- package/build/reporter.js.map +1 -1
- package/build/runner.cjs +23 -19
- package/build/runner.cjs.map +1 -1
- package/build/runner.js +23 -19
- package/build/runner.js.map +1 -1
- package/build/types.cjs +1 -1
- package/build/types.cjs.map +1 -1
- package/build/types.d.ts +1 -1
- package/build/types.js +1 -1
- package/build/types.js.map +1 -1
- package/build/utils.cjs +53 -0
- package/build/utils.cjs.map +1 -1
- package/build/utils.d.ts +2 -0
- package/build/utils.js +48 -1
- package/build/utils.js.map +1 -1
- package/build/worker.cjs +6 -9
- package/build/worker.cjs.map +1 -1
- package/build/worker.js +7 -10
- package/build/worker.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/assert-no-closure.js +134 -0
- package/src/executor.ts +12 -7
- package/src/index.ts +50 -11
- package/src/reporter.ts +62 -118
- package/src/runner.ts +26 -20
- package/src/types.ts +1 -1
- package/src/utils.ts +56 -1
- package/src/worker.ts +7 -9
package/src/reporter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { div, max, divs } from './utils.js';
|
|
2
|
-
import { ReportType, DURATION_SCALE
|
|
1
|
+
import { div, max, divs, isqrt } from './utils.js';
|
|
2
|
+
import { ReportType, DURATION_SCALE } from './types.js';
|
|
3
3
|
|
|
4
4
|
const units = [
|
|
5
5
|
{ unit: 'ns', factor: 1 },
|
|
@@ -70,6 +70,24 @@ export class Report {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const SQRT_SCALE = 1_000_000n;
|
|
74
|
+
const SQRT_SCALE_SQ = SQRT_SCALE * SQRT_SCALE;
|
|
75
|
+
const Z95_NUM = 196n;
|
|
76
|
+
const Z95_DENOM = 100n;
|
|
77
|
+
|
|
78
|
+
const computeStats = (durations: BigUint64Array) => {
|
|
79
|
+
let sum = 0n;
|
|
80
|
+
for (const d of durations) sum += d;
|
|
81
|
+
const n = BigInt(durations.length);
|
|
82
|
+
const mean = sum / n;
|
|
83
|
+
let ssd = 0n;
|
|
84
|
+
for (const d of durations) {
|
|
85
|
+
const diff = d - mean;
|
|
86
|
+
ssd += diff * diff;
|
|
87
|
+
}
|
|
88
|
+
return { sum, mean, ssd, n };
|
|
89
|
+
};
|
|
90
|
+
|
|
73
91
|
export const createReport = (durations: BigUint64Array, type: ReportType): Report => {
|
|
74
92
|
const n = durations.length;
|
|
75
93
|
if (n === 0) {
|
|
@@ -112,11 +130,7 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
case 'ops': {
|
|
115
|
-
|
|
116
|
-
for (const duration of durations) {
|
|
117
|
-
sum += duration;
|
|
118
|
-
}
|
|
119
|
-
const avgScaled = sum / BigInt(n);
|
|
133
|
+
const { mean: avgScaled, ssd, n: nBig } = computeStats(durations);
|
|
120
134
|
const nsPerSecScaled = 1_000_000_000n * DURATION_SCALE;
|
|
121
135
|
const raw = Number(nsPerSecScaled) / Number(avgScaled);
|
|
122
136
|
const extra = raw < 1 ? Math.ceil(-Math.log10(raw)) : 0;
|
|
@@ -126,111 +140,61 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
126
140
|
const scale = 10n ** BigInt(exp);
|
|
127
141
|
|
|
128
142
|
const value = avgScaled > 0n ? (nsPerSecScaled * scale) / avgScaled : 0n;
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
let uncertainty = 0;
|
|
144
|
+
if (n >= 2 && avgScaled > 0n) {
|
|
145
|
+
const RME_PRECISION = 1_000_000n;
|
|
146
|
+
const semOverMeanSqScaled = (ssd * RME_PRECISION * RME_PRECISION) / (BigInt(n - 1) * nBig * avgScaled * avgScaled);
|
|
147
|
+
const semOverMeanScaled = isqrt(semOverMeanSqScaled);
|
|
148
|
+
uncertainty = Number(Z95_NUM * semOverMeanScaled) / Number(RME_PRECISION);
|
|
149
|
+
}
|
|
131
150
|
return new Report(type, value, uncertainty, scale);
|
|
132
151
|
}
|
|
133
152
|
case 'mean': {
|
|
134
|
-
|
|
135
|
-
for (const duration of durations) {
|
|
136
|
-
sum += duration;
|
|
137
|
-
}
|
|
153
|
+
const { sum } = computeStats(durations);
|
|
138
154
|
const value = divs(sum, BigInt(n), 1n);
|
|
139
155
|
return new Report(type, value, 0, DURATION_SCALE);
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
case 'variance': {
|
|
143
159
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE * DURATION_SCALE);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sum += duration;
|
|
147
|
-
}
|
|
148
|
-
const mean = sum / BigInt(n);
|
|
149
|
-
let sumSquaredDiff = 0n;
|
|
150
|
-
for (const duration of durations) {
|
|
151
|
-
const diff = duration - mean;
|
|
152
|
-
sumSquaredDiff += diff * diff;
|
|
153
|
-
}
|
|
154
|
-
const variance = sumSquaredDiff / BigInt(n - 1);
|
|
160
|
+
const { ssd } = computeStats(durations);
|
|
161
|
+
const variance = ssd / BigInt(n - 1);
|
|
155
162
|
return new Report(type, variance, 0, DURATION_SCALE * DURATION_SCALE);
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
case 'sd': {
|
|
159
166
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const mean = sum / BigInt(n);
|
|
165
|
-
let sumSquaredDiff = 0n;
|
|
166
|
-
for (const duration of durations) {
|
|
167
|
-
const diff = duration - mean;
|
|
168
|
-
sumSquaredDiff += diff * diff;
|
|
169
|
-
}
|
|
170
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
171
|
-
const sd = Math.sqrt(variance);
|
|
172
|
-
const sdScaled = BigInt(Math.round(sd));
|
|
173
|
-
return new Report(type, sdScaled, 0, DURATION_SCALE);
|
|
167
|
+
const { ssd } = computeStats(durations);
|
|
168
|
+
const scaledVariance = (ssd * SQRT_SCALE_SQ) / BigInt(n - 1);
|
|
169
|
+
const sdScaled = isqrt(scaledVariance);
|
|
170
|
+
return new Report(type, sdScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
case 'sem': {
|
|
177
174
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const mean = sum / BigInt(n);
|
|
183
|
-
let sumSquaredDiff = 0n;
|
|
184
|
-
for (const duration of durations) {
|
|
185
|
-
const diff = duration - mean;
|
|
186
|
-
sumSquaredDiff += diff * diff;
|
|
187
|
-
}
|
|
188
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
189
|
-
const sd = Math.sqrt(variance);
|
|
190
|
-
const sem = sd / Math.sqrt(n);
|
|
191
|
-
const semScaled = BigInt(Math.round(sem));
|
|
192
|
-
return new Report(type, semScaled, 0, DURATION_SCALE);
|
|
175
|
+
const { ssd, n: nBig } = computeStats(durations);
|
|
176
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
177
|
+
const semScaled = isqrt(semSqScaled);
|
|
178
|
+
return new Report(type, semScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
193
179
|
}
|
|
194
180
|
|
|
195
181
|
case 'moe': {
|
|
196
182
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let sumSquaredDiff = 0n;
|
|
203
|
-
for (const duration of durations) {
|
|
204
|
-
const diff = duration - mean;
|
|
205
|
-
sumSquaredDiff += diff * diff;
|
|
206
|
-
}
|
|
207
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
208
|
-
const sd = Math.sqrt(variance);
|
|
209
|
-
const sem = sd / Math.sqrt(n);
|
|
210
|
-
const moe = Z95 * sem;
|
|
211
|
-
const moeScaled = BigInt(Math.round(moe));
|
|
212
|
-
return new Report(type, moeScaled, 0, DURATION_SCALE);
|
|
183
|
+
const { ssd, n: nBig } = computeStats(durations);
|
|
184
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
185
|
+
const semScaled = isqrt(semSqScaled);
|
|
186
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
187
|
+
return new Report(type, moeScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
213
188
|
}
|
|
214
189
|
|
|
215
190
|
case 'rme': {
|
|
216
191
|
if (n < 2) return new Report(type, 0n);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
let sumSquaredDiff = 0;
|
|
224
|
-
for (const duration of durations) {
|
|
225
|
-
const diff = Number(duration) - mean;
|
|
226
|
-
sumSquaredDiff += diff * diff;
|
|
227
|
-
}
|
|
228
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
229
|
-
const sd = Math.sqrt(variance);
|
|
230
|
-
const sem = sd / Math.sqrt(n);
|
|
231
|
-
const moe = Z95 * sem;
|
|
232
|
-
const rme = (moe / mean) * 100;
|
|
233
|
-
const rmeScaled = BigInt(Math.round(rme * 100));
|
|
192
|
+
const { mean, ssd, n: nBig } = computeStats(durations);
|
|
193
|
+
if (mean === 0n) return new Report(type, 0n);
|
|
194
|
+
const RME_PRECISION = 1_000_000n;
|
|
195
|
+
const semOverMeanSqScaled = (ssd * RME_PRECISION * RME_PRECISION) / (BigInt(n - 1) * nBig * mean * mean);
|
|
196
|
+
const semOverMeanScaled = isqrt(semOverMeanSqScaled);
|
|
197
|
+
const rmeScaled = (Z95_NUM * semOverMeanScaled * 100n) / RME_PRECISION;
|
|
234
198
|
return new Report(type, rmeScaled, 0, 100n);
|
|
235
199
|
}
|
|
236
200
|
|
|
@@ -259,42 +223,22 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
259
223
|
|
|
260
224
|
case 'ci_lower': {
|
|
261
225
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
for (const duration of durations) {
|
|
269
|
-
const diff = Number(duration) - mean;
|
|
270
|
-
sumSquaredDiff += diff * diff;
|
|
271
|
-
}
|
|
272
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
273
|
-
const sd = Math.sqrt(variance);
|
|
274
|
-
const sem = sd / Math.sqrt(n);
|
|
275
|
-
const moe = Z95 * sem;
|
|
276
|
-
const ciLower = Math.max(0, mean - moe);
|
|
277
|
-
return new Report(type, BigInt(Math.round(ciLower)), 0, DURATION_SCALE);
|
|
226
|
+
const { mean, ssd, n: nBig } = computeStats(durations);
|
|
227
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
228
|
+
const semScaled = isqrt(semSqScaled);
|
|
229
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
230
|
+
const ciLowerScaled = mean * SQRT_SCALE - moeScaled;
|
|
231
|
+
return new Report(type, ciLowerScaled > 0n ? ciLowerScaled : 0n, 0, DURATION_SCALE * SQRT_SCALE);
|
|
278
232
|
}
|
|
279
233
|
|
|
280
234
|
case 'ci_upper': {
|
|
281
235
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
for (const duration of durations) {
|
|
289
|
-
const diff = Number(duration) - mean;
|
|
290
|
-
sumSquaredDiff += diff * diff;
|
|
291
|
-
}
|
|
292
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
293
|
-
const sd = Math.sqrt(variance);
|
|
294
|
-
const sem = sd / Math.sqrt(n);
|
|
295
|
-
const moe = Z95 * sem;
|
|
296
|
-
const ciUpper = mean + moe;
|
|
297
|
-
return new Report(type, BigInt(Math.round(ciUpper)), 0, DURATION_SCALE);
|
|
236
|
+
const { mean, ssd, n: nBig } = computeStats(durations);
|
|
237
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
238
|
+
const semScaled = isqrt(semSqScaled);
|
|
239
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
240
|
+
const ciUpperScaled = mean * SQRT_SCALE + moeScaled;
|
|
241
|
+
return new Report(type, ciUpperScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
298
242
|
}
|
|
299
243
|
|
|
300
244
|
default: {
|
package/src/runner.ts
CHANGED
|
@@ -7,7 +7,7 @@ const COMPLETE_VALUE = 100_00;
|
|
|
7
7
|
|
|
8
8
|
const hr = process.hrtime.bigint.bind(process.hrtime);
|
|
9
9
|
|
|
10
|
-
const sink = new Int32Array(
|
|
10
|
+
const sink = new Int32Array(1);
|
|
11
11
|
const consume = (value: unknown) => {
|
|
12
12
|
let payload = 0;
|
|
13
13
|
switch (typeof value) {
|
|
@@ -32,7 +32,7 @@ const consume = (value: unknown) => {
|
|
|
32
32
|
default:
|
|
33
33
|
payload = -1;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
sink[0] ^= payload;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
const runSync = (run: Function, overhead: bigint) => {
|
|
@@ -518,13 +518,23 @@ export const benchmark = async <TContext, TInput>({
|
|
|
518
518
|
}
|
|
519
519
|
|
|
520
520
|
let i = 0;
|
|
521
|
-
|
|
522
|
-
let
|
|
521
|
+
const WELFORD_SCALE = 1_000_000n;
|
|
522
|
+
let meanS = 0n;
|
|
523
|
+
let m2S = 0n;
|
|
523
524
|
const outlierWindow: number[] = [];
|
|
524
525
|
let skipped = 0;
|
|
525
526
|
const maxSkipped = maxCycles * 10;
|
|
526
527
|
let disableFiltering = false;
|
|
527
528
|
|
|
529
|
+
const absThScaled = BigInt(Math.round(absThreshold)) * WELFORD_SCALE;
|
|
530
|
+
const absThSq = absThScaled * absThScaled;
|
|
531
|
+
const REL_PRECISION = 1_000_000n;
|
|
532
|
+
const relThBigint = BigInt(Math.round(relThreshold * Number(REL_PRECISION)));
|
|
533
|
+
const relThSq = relThBigint * relThBigint;
|
|
534
|
+
const relPrecSq = REL_PRECISION * REL_PRECISION;
|
|
535
|
+
const Z95_SQ_NUM = 38416n;
|
|
536
|
+
const Z95_SQ_DENOM = 10000n;
|
|
537
|
+
|
|
528
538
|
while (true) {
|
|
529
539
|
if (i >= maxCycles) break;
|
|
530
540
|
if (!disableFiltering && skipped >= maxSkipped) {
|
|
@@ -583,26 +593,28 @@ export const benchmark = async <TContext, TInput>({
|
|
|
583
593
|
}
|
|
584
594
|
|
|
585
595
|
const durationNumber = Number(sampleDuration);
|
|
586
|
-
pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
|
|
587
596
|
if (!disableFiltering) {
|
|
588
597
|
const { median, iqr } = medianAndIqr(outlierWindow);
|
|
598
|
+
pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
|
|
589
599
|
const maxAllowed = median + OUTLIER_IQR_MULTIPLIER * iqr || Number.POSITIVE_INFINITY;
|
|
590
600
|
if (outlierWindow.length >= 8 && durationNumber > maxAllowed && durationNumber - median > OUTLIER_ABS_THRESHOLD) {
|
|
591
601
|
skipped++;
|
|
592
602
|
continue;
|
|
593
603
|
}
|
|
594
604
|
|
|
595
|
-
const meanNumber = Number(
|
|
605
|
+
const meanNumber = Number(meanS / WELFORD_SCALE);
|
|
596
606
|
if (i >= 8 && meanNumber > 0 && durationNumber > OUTLIER_MULTIPLIER * meanNumber && durationNumber - meanNumber > OUTLIER_ABS_THRESHOLD) {
|
|
597
607
|
skipped++;
|
|
598
608
|
continue;
|
|
599
609
|
}
|
|
610
|
+
} else {
|
|
611
|
+
pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
|
|
600
612
|
}
|
|
601
613
|
|
|
602
614
|
durations[i++] = sampleDuration;
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
615
|
+
const deltaS = sampleDuration * WELFORD_SCALE - meanS;
|
|
616
|
+
meanS += deltaS / BigInt(i);
|
|
617
|
+
m2S += deltaS * (sampleDuration * WELFORD_SCALE - meanS);
|
|
606
618
|
|
|
607
619
|
const progress = (i / maxCycles) * COMPLETE_VALUE;
|
|
608
620
|
if (i % PROGRESS_STRIDE === 0) {
|
|
@@ -610,17 +622,11 @@ export const benchmark = async <TContext, TInput>({
|
|
|
610
622
|
}
|
|
611
623
|
|
|
612
624
|
if (i >= minCycles) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const meanNum = Number(mean);
|
|
620
|
-
const cov = stddev / (meanNum || 1);
|
|
621
|
-
if (cov <= relThreshold) {
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
625
|
+
if (m2S <= absThSq * BigInt(i - 1)) break;
|
|
626
|
+
// RME convergence: Z95 * sem/mean <= relThreshold
|
|
627
|
+
// Z95^2 * m2S / (n*(n-1)*meanS^2) <= relThreshold^2
|
|
628
|
+
const ni = BigInt(i);
|
|
629
|
+
if (meanS !== 0n && Z95_SQ_NUM * m2S * relPrecSq <= relThSq * ni * (ni - 1n) * meanS * meanS * Z95_SQ_DENOM) break;
|
|
624
630
|
}
|
|
625
631
|
}
|
|
626
632
|
|
package/src/types.ts
CHANGED
|
@@ -107,7 +107,7 @@ export enum Control {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export const CONTROL_SLOTS = Object.values(Control).length / 2;
|
|
110
|
-
export const DEFAULT_CYCLES =
|
|
110
|
+
export const DEFAULT_CYCLES = 10_000;
|
|
111
111
|
export const Z95 = 1.96;
|
|
112
112
|
export const DURATION_SCALE = 1000n;
|
|
113
113
|
export const COMPLETE_VALUE = 100_00;
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import { transform } from '@swc/core';
|
|
1
|
+
import { transform, parseSync } from '@swc/core';
|
|
2
|
+
|
|
3
|
+
export const isqrt = (n: bigint): bigint => {
|
|
4
|
+
if (n < 0n) throw new RangeError('Square root of negative');
|
|
5
|
+
if (n < 2n) return n;
|
|
6
|
+
let x = n;
|
|
7
|
+
let y = (x + 1n) >> 1n;
|
|
8
|
+
while (y < x) {
|
|
9
|
+
x = y;
|
|
10
|
+
y = (x + n / x) >> 1n;
|
|
11
|
+
}
|
|
12
|
+
return x;
|
|
13
|
+
};
|
|
2
14
|
|
|
3
15
|
export const abs = (value: bigint) => {
|
|
4
16
|
if (value < 0n) {
|
|
@@ -66,6 +78,49 @@ export class ScaledBigInt {
|
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
|
|
81
|
+
const KNOWN_GLOBALS = new Set(Object.getOwnPropertyNames(globalThis));
|
|
82
|
+
KNOWN_GLOBALS.add('arguments');
|
|
83
|
+
|
|
84
|
+
function collectUnresolved(node: unknown, result: Set<string>) {
|
|
85
|
+
if (!node || typeof node !== 'object') return;
|
|
86
|
+
if (Array.isArray(node)) {
|
|
87
|
+
for (const item of node) collectUnresolved(item, result);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const obj = node as Record<string, unknown>;
|
|
91
|
+
if (obj.type === 'Identifier' && obj.ctxt === 1 && typeof obj.value === 'string') {
|
|
92
|
+
result.add(obj.value);
|
|
93
|
+
}
|
|
94
|
+
for (const key of Object.keys(obj)) {
|
|
95
|
+
if (key === 'span') continue;
|
|
96
|
+
collectUnresolved(obj[key], result);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function assertNoClosure(code: string, name: string): void {
|
|
101
|
+
let ast;
|
|
102
|
+
try {
|
|
103
|
+
ast = parseSync(`var __fn = ${code}`, { syntax: 'ecmascript', target: 'esnext' });
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const unresolved = new Set<string>();
|
|
108
|
+
collectUnresolved(ast, unresolved);
|
|
109
|
+
for (const g of KNOWN_GLOBALS) unresolved.delete(g);
|
|
110
|
+
if (unresolved.size === 0) return;
|
|
111
|
+
|
|
112
|
+
const vars = [...unresolved].join(', ');
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Benchmark "${name}" function references outer-scope variables: ${vars}\n\n` +
|
|
115
|
+
`Benchmark functions are serialized with .toString() and executed in an isolated\n` +
|
|
116
|
+
`worker thread. Closed-over variables from the original module scope are not\n` +
|
|
117
|
+
`available in the worker and will cause a ReferenceError at runtime.\n\n` +
|
|
118
|
+
`To fix this, move the referenced values into:\n` +
|
|
119
|
+
` - "setup" function (returned value becomes the first argument of run/pre/post)\n` +
|
|
120
|
+
` - "data" option (passed as the second argument of run/pre/post)`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
69
124
|
export const transpile = async (code: string): Promise<string> => {
|
|
70
125
|
const output = await transform(code, {
|
|
71
126
|
filename: 'benchmark.ts',
|
package/src/worker.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { workerData } from 'node:worker_threads';
|
|
2
|
-
import { SourceTextModule, SyntheticModule
|
|
2
|
+
import { SourceTextModule, SyntheticModule } from 'node:vm';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { isAbsolute } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -41,7 +41,11 @@ const resolveSpecifier = (specifier: string) => {
|
|
|
41
41
|
if (isAbsolute(specifier)) {
|
|
42
42
|
return pathToFileURL(specifier).href;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
try {
|
|
45
|
+
return requireFrom.resolve(specifier);
|
|
46
|
+
} catch {
|
|
47
|
+
return specifier;
|
|
48
|
+
}
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const source = `
|
|
@@ -52,11 +56,6 @@ export const run = ${serialize(runCode)};
|
|
|
52
56
|
export const post = ${serialize(postCode)};
|
|
53
57
|
`;
|
|
54
58
|
|
|
55
|
-
const globals = Object.create(null);
|
|
56
|
-
for (const k of Object.getOwnPropertyNames(globalThis)) {
|
|
57
|
-
globals[k] = (globalThis as any)[k];
|
|
58
|
-
}
|
|
59
|
-
const context = createContext(globals);
|
|
60
59
|
const imports = new Map<string, SyntheticModule>();
|
|
61
60
|
|
|
62
61
|
const createSyntheticModule = (moduleExports: unknown, exportNames: string[], identifier: string) => {
|
|
@@ -71,7 +70,7 @@ const createSyntheticModule = (moduleExports: unknown, exportNames: string[], id
|
|
|
71
70
|
mod.setExport(name, (moduleExports as Record<string, unknown>)[name]);
|
|
72
71
|
}
|
|
73
72
|
},
|
|
74
|
-
{ identifier
|
|
73
|
+
{ identifier },
|
|
75
74
|
);
|
|
76
75
|
return mod;
|
|
77
76
|
};
|
|
@@ -111,7 +110,6 @@ const loadDynamicModule = async (target: string) => {
|
|
|
111
110
|
};
|
|
112
111
|
const mod = new SourceTextModule(source, {
|
|
113
112
|
identifier: resolvedBenchmarkUrl,
|
|
114
|
-
context,
|
|
115
113
|
initializeImportMeta(meta) {
|
|
116
114
|
meta.url = resolvedBenchmarkUrl;
|
|
117
115
|
},
|