perfshield 0.0.8 → 0.0.9

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.
Files changed (2) hide show
  1. package/lib/runner.js +93 -95
  2. package/package.json +1 -1
package/lib/runner.js CHANGED
@@ -37,43 +37,37 @@ const sleep = async delayMs => {
37
37
  setTimeout(resolve, delayMs);
38
38
  });
39
39
  };
40
- const warmupBenchmarks = async (harness, benchmarks, delayMs, progress) => {
41
- const warmups = [];
42
- for (let index = 0; index < benchmarks.length; index += 1) {
43
- const descriptor = benchmarks[index];
44
- const order = getVersionOrder(index);
45
- let baselineSample;
46
- let currentSample;
47
- for (const version of order) {
48
- const result = await harness.runSample({
49
- index,
50
- iterations: descriptor.iterations,
51
- version
52
- });
53
- if (version === "baseline") {
54
- baselineSample = result.durationMs;
55
- } else {
56
- currentSample = result.durationMs;
57
- }
58
- }
59
- if (baselineSample == null || currentSample == null) {
60
- throw new Error("Warmup did not collect baseline/current samples.");
61
- }
62
- warmups.push({
63
- baseline: baselineSample,
64
- current: currentSample
40
+ const warmupBenchmark = async (harness, benchmark, index, delayMs, progress) => {
41
+ const order = getVersionOrder(index);
42
+ let baselineSample;
43
+ let currentSample;
44
+ for (const version of order) {
45
+ const result = await harness.runSample({
46
+ index,
47
+ iterations: benchmark.iterations,
48
+ version
65
49
  });
66
- if (progress) {
67
- progress({
68
- benchmarkCount: benchmarks.length,
69
- benchmarkIndex: index,
70
- benchmarkName: descriptor.name,
71
- phase: "warmup"
72
- });
50
+ if (version === "baseline") {
51
+ baselineSample = result.durationMs;
52
+ } else {
53
+ currentSample = result.durationMs;
73
54
  }
74
- await sleep(delayMs);
75
55
  }
76
- return warmups;
56
+ if (baselineSample == null || currentSample == null) {
57
+ throw new Error("Warmup did not collect baseline/current samples.");
58
+ }
59
+ if (progress) {
60
+ progress({
61
+ benchmarkIndex: index,
62
+ benchmarkName: benchmark.name,
63
+ phase: "warmup"
64
+ });
65
+ }
66
+ await sleep(delayMs);
67
+ return {
68
+ baseline: baselineSample,
69
+ current: currentSample
70
+ };
77
71
  };
78
72
  const computeIterationOverrides = (benchmarks, warmups, minTimeMs) => {
79
73
  if (minTimeMs <= 0) {
@@ -138,36 +132,26 @@ const runSamplePair = async (harness, index, iterations, order) => {
138
132
  current: currentSample
139
133
  };
140
134
  };
141
- const collectSamples = async (harness, benchmarks, minSamples, iterationOverrides, delayMs, minTimeMs, samples, progress) => {
142
- const buckets = samples ?? benchmarks.map(() => ({
143
- baseline: [],
144
- current: []
145
- }));
146
- let completed = 0;
147
- const total = minSamples * benchmarks.length;
135
+ const collectSamplesForBenchmark = async (harness, benchmark, index, minSamples, iterationOverrides, delayMs, minTimeMs, bucket, progress, progressState) => {
148
136
  for (let iteration = 0; iteration < minSamples; iteration += 1) {
149
137
  const order = getVersionOrder(iteration);
150
- const indexOrder = buildIndexOrder(benchmarks.length, iteration);
151
- for (const index of indexOrder) {
152
- const iterations = iterationOverrides[index];
153
- const minimumIterations = benchmarks[index].iterations ?? 1;
154
- const result = await runSamplePair(harness, index, iterations, order);
155
- buckets[index].baseline.push(result.baseline);
156
- buckets[index].current.push(result.current);
157
- const nextIterations = updateIterations(iterations ?? minimumIterations, result.baseline, result.current, minTimeMs, minimumIterations);
158
- iterationOverrides[index] = nextIterations;
159
- completed += 1;
160
- if (progress) {
161
- progress({
162
- completed,
163
- phase: "samples",
164
- total
165
- });
166
- }
167
- await sleep(delayMs);
138
+ const iterations = iterationOverrides[index];
139
+ const minimumIterations = benchmark.iterations ?? 1;
140
+ const result = await runSamplePair(harness, index, iterations, order);
141
+ bucket.baseline.push(result.baseline);
142
+ bucket.current.push(result.current);
143
+ const nextIterations = updateIterations(iterations ?? minimumIterations, result.baseline, result.current, minTimeMs, minimumIterations);
144
+ iterationOverrides[index] = nextIterations;
145
+ if (progress && progressState) {
146
+ progressState.completed += 1;
147
+ progress({
148
+ completed: progressState.completed,
149
+ phase: "samples",
150
+ total: progressState.total
151
+ });
168
152
  }
153
+ await sleep(delayMs);
169
154
  }
170
- return buckets;
171
155
  };
172
156
  const intervalContains = (interval, value) => interval.low <= value && value <= interval.high;
173
157
  const autoSampleResolved = (samples, conditions, maxRelativeMargin) => samples.every(bucket => {
@@ -189,36 +173,33 @@ const autoSampleResolved = (samples, conditions, maxRelativeMargin) => samples.e
189
173
  }
190
174
  return true;
191
175
  });
192
- const autoSample = async (harness, benchmarks, samples, conditions, maxRelativeMargin, iterationOverrides, delayMs, minTimeMs, progress, timeoutMs) => {
176
+ const autoSampleForBenchmark = async (harness, benchmark, index, bucket, conditions, maxRelativeMargin, iterationOverrides, delayMs, minTimeMs, progress, timeoutMs) => {
193
177
  const startTime = Date.now();
194
178
  let roundRobinSeed = 0;
195
179
  let completed = 0;
196
180
  while (Date.now() - startTime < timeoutMs) {
197
- if (autoSampleResolved(samples, conditions, maxRelativeMargin)) {
181
+ if (autoSampleResolved([bucket], conditions, maxRelativeMargin)) {
198
182
  return;
199
183
  }
200
184
  for (let batch = 0; batch < autoSampleBatchSize; batch += 1) {
201
185
  const order = getVersionOrder(roundRobinSeed);
202
- const indexOrder = buildIndexOrder(benchmarks.length, roundRobinSeed);
203
186
  roundRobinSeed += 1;
204
- for (const index of indexOrder) {
205
- const iterations = iterationOverrides[index];
206
- const minimumIterations = benchmarks[index].iterations ?? 1;
207
- const result = await runSamplePair(harness, index, iterations, order);
208
- samples[index].baseline.push(result.baseline);
209
- samples[index].current.push(result.current);
210
- const nextIterations = updateIterations(iterations ?? minimumIterations, result.baseline, result.current, minTimeMs, minimumIterations);
211
- iterationOverrides[index] = nextIterations;
212
- completed += 1;
213
- if (progress) {
214
- progress({
215
- completed,
216
- elapsedMs: Date.now() - startTime,
217
- phase: "autosample"
218
- });
219
- }
220
- await sleep(delayMs);
187
+ const iterations = iterationOverrides[index];
188
+ const minimumIterations = benchmark.iterations ?? 1;
189
+ const result = await runSamplePair(harness, index, iterations, order);
190
+ bucket.baseline.push(result.baseline);
191
+ bucket.current.push(result.current);
192
+ const nextIterations = updateIterations(iterations ?? minimumIterations, result.baseline, result.current, minTimeMs, minimumIterations);
193
+ iterationOverrides[index] = nextIterations;
194
+ completed += 1;
195
+ if (progress) {
196
+ progress({
197
+ completed,
198
+ elapsedMs: Date.now() - startTime,
199
+ phase: "autosample"
200
+ });
221
201
  }
202
+ await sleep(delayMs);
222
203
  }
223
204
  }
224
205
  };
@@ -242,24 +223,41 @@ export const runEngineComparison = async options => {
242
223
  const effectiveMinTimeMs = minTimeMs / Math.max(1, sampleScale * benchmarkScale);
243
224
  const delayMs = config.sampling.delayMs ?? 0;
244
225
  const maxRelativeMargin = config.sampling.maxRelativeMargin ?? defaultMaxRelativeMargin;
245
- const warmups = await warmupBenchmarks(harness, benchmarks, delayMs, options.progress);
246
- const iterationOverrides = computeIterationOverrides(benchmarks, warmups, effectiveMinTimeMs);
247
- const samples = warmups.map(warmup => ({
248
- baseline: [warmup.baseline],
249
- current: [warmup.current]
250
- }));
251
226
  const remainingSamples = Math.max(0, config.sampling.minSamples - 1);
252
- if (remainingSamples > 0) {
253
- await collectSamples(harness, benchmarks, remainingSamples, iterationOverrides, delayMs, effectiveMinTimeMs, samples, options.progress);
254
- }
255
- await autoSample(harness, benchmarks, samples, config.sampling.conditions, maxRelativeMargin, iterationOverrides, delayMs, effectiveMinTimeMs, options.progress, config.sampling.timeoutMs);
256
- const benchmarkResults = benchmarks.map((benchmark, index) => {
257
- const baselineSamples = samples[index].baseline;
258
- const currentSamples = samples[index].current;
227
+ const progressState = {
228
+ completed: 0,
229
+ total: remainingSamples * benchmarks.length
230
+ };
231
+ const benchmarkResults = new Array(benchmarks.length);
232
+ const iterationOverrides = benchmarks.map(() => undefined);
233
+ const benchmarkOrder = buildIndexOrder(benchmarks.length, 0);
234
+ const autoSampleDeadline = Date.now() + config.sampling.timeoutMs;
235
+ for (const index of benchmarkOrder) {
236
+ const benchmark = benchmarks[index];
237
+ const progress = options.progress;
238
+ const warmupSample = await warmupBenchmark(harness, benchmark, index, delayMs, progress ? event => progress({
239
+ ...event,
240
+ benchmarkCount: benchmarks.length
241
+ }) : undefined);
242
+ const iterationOverride = computeIterationOverrides([benchmark], [warmupSample], effectiveMinTimeMs)[0];
243
+ iterationOverrides[index] = iterationOverride;
244
+ const bucket = {
245
+ baseline: [warmupSample.baseline],
246
+ current: [warmupSample.current]
247
+ };
248
+ if (remainingSamples > 0) {
249
+ await collectSamplesForBenchmark(harness, benchmark, index, remainingSamples, iterationOverrides, delayMs, effectiveMinTimeMs, bucket, progress, progressState);
250
+ }
251
+ const remainingTimeoutMs = Math.max(0, autoSampleDeadline - Date.now());
252
+ if (remainingTimeoutMs > 0) {
253
+ await autoSampleForBenchmark(harness, benchmark, index, bucket, config.sampling.conditions, maxRelativeMargin, iterationOverrides, delayMs, effectiveMinTimeMs, progress, remainingTimeoutMs);
254
+ }
255
+ const baselineSamples = bucket.baseline;
256
+ const currentSamples = bucket.current;
259
257
  const baselineStats = summaryStats(baselineSamples);
260
258
  const currentStats = summaryStats(currentSamples);
261
259
  const difference = computeRelativeDifferenceFromSamples(baselineSamples, currentSamples);
262
- return {
260
+ benchmarkResults[index] = {
263
261
  benchmark,
264
262
  difference,
265
263
  samples: {
@@ -271,7 +269,7 @@ export const runEngineComparison = async options => {
271
269
  current: currentStats
272
270
  }
273
271
  };
274
- });
272
+ }
275
273
  return {
276
274
  benchmarks: benchmarkResults,
277
275
  engine
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfshield",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "A tool for doing web benchmarking across multiple JS engines and with statistical signifigance",
5
5
  "license": "MIT",
6
6
  "type": "module",