summaryrefslogtreecommitdiff
path: root/runtime/js
diff options
context:
space:
mode:
authorevan <github@evan.lol>2022-04-20 22:06:39 +0300
committerGitHub <noreply@github.com>2022-04-20 21:06:39 +0200
commitf785ecee1a68faac517a6d35763dd26ef033d722 (patch)
treeae0bca3d8cf79088b42b0b8d9a9a5efbe89a27b0 /runtime/js
parent2612b6f20fc21fb92402aa9086d13a7192ae3814 (diff)
feat(bench): update API, new console reporter (#14305)
This commit changes "deno bench" subcommand, by updating the "Deno.bench" API as follows: - remove "Deno.BenchDefinition.n" - remove "Deno.BenchDefintion.warmup" - add "Deno.BenchDefinition.group" - add "Deno.BenchDefintion.baseline" This is done because bench cases are no longer run fixed amount of iterations, but instead they are run until there is difference between subsequent runs that is statistically insiginificant. Additionally, console reporter was rewritten completely, to looks similar to "hyperfine" reporter.
Diffstat (limited to 'runtime/js')
-rw-r--r--runtime/js/40_testing.js271
1 files changed, 202 insertions, 69 deletions
diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js
index c5adc1f59..7a5162e7f 100644
--- a/runtime/js/40_testing.js
+++ b/runtime/js/40_testing.js
@@ -9,16 +9,20 @@
const { assert } = window.__bootstrap.infra;
const {
AggregateErrorPrototype,
+ ArrayFrom,
ArrayPrototypeFilter,
ArrayPrototypeJoin,
+ ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSome,
+ ArrayPrototypeSort,
DateNow,
Error,
FunctionPrototype,
Map,
MapPrototypeHas,
+ MathCeil,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
Promise,
@@ -434,6 +438,27 @@
};
}
+ function assertExitSync(fn, isTest) {
+ return function exitSanitizer(...params) {
+ setExitHandler((exitCode) => {
+ assert(
+ false,
+ `${
+ isTest ? "Test case" : "Bench"
+ } attempted to exit with exit code: ${exitCode}`,
+ );
+ });
+
+ try {
+ fn(...new SafeArrayIterator(params));
+ } catch (err) {
+ throw err;
+ } finally {
+ setExitHandler(null);
+ }
+ };
+ }
+
function assertTestStepScopes(fn) {
/** @param step {TestStep} */
return async function testStepSanitizer(step) {
@@ -721,18 +746,14 @@
benchDef = { ...defaults, ...nameOrFnOrOptions, fn, name };
}
+ const AsyncFunction = (async () => {}).constructor;
+ benchDef.async = AsyncFunction === benchDef.fn.constructor;
+
benchDef.fn = wrapBenchFnWithSanitizers(
- reportBenchIteration(benchDef.fn),
+ benchDef.fn,
benchDef,
);
- if (benchDef.permissions) {
- benchDef.fn = withPermissions(
- benchDef.fn,
- benchDef.permissions,
- );
- }
-
ArrayPrototypePush(benches, benchDef);
}
@@ -823,37 +844,166 @@
}
}
- async function runBench(bench) {
- if (bench.ignore) {
- return "ignored";
+ function compareMeasurements(a, b) {
+ if (a > b) return 1;
+ if (a < b) return -1;
+
+ return 0;
+ }
+
+ function benchStats(n, highPrecision, avg, min, max, all) {
+ return {
+ n,
+ min,
+ max,
+ p75: all[MathCeil(n * (75 / 100)) - 1],
+ p99: all[MathCeil(n * (99 / 100)) - 1],
+ p995: all[MathCeil(n * (99.5 / 100)) - 1],
+ p999: all[MathCeil(n * (99.9 / 100)) - 1],
+ avg: !highPrecision ? (avg / n) : MathCeil(avg / n),
+ };
+ }
+
+ async function benchMeasure(timeBudget, fn, step, sync) {
+ let n = 0;
+ let avg = 0;
+ let wavg = 0;
+ const all = [];
+ let min = Infinity;
+ let max = -Infinity;
+ const lowPrecisionThresholdInNs = 1e4;
+
+ // warmup step
+ let c = 0;
+ step.warmup = true;
+ let iterations = 20;
+ let budget = 10 * 1e6;
+
+ if (sync) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ fn();
+ const iterationTime = benchNow() - t1;
+
+ c++;
+ wavg += iterationTime;
+ budget -= iterationTime;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ await fn();
+ const iterationTime = benchNow() - t1;
+
+ c++;
+ wavg += iterationTime;
+ budget -= iterationTime;
+ }
+ }
+
+ wavg /= c;
+
+ // measure step
+ step.warmup = false;
+
+ if (wavg > lowPrecisionThresholdInNs) {
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
+
+ if (sync) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ fn();
+ const iterationTime = benchNow() - t1;
+
+ n++;
+ avg += iterationTime;
+ budget -= iterationTime;
+ all.push(iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ await fn();
+ const iterationTime = benchNow() - t1;
+
+ n++;
+ avg += iterationTime;
+ budget -= iterationTime;
+ all.push(iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ }
+ }
+ } else {
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
+
+ if (sync) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn();
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ all.push(iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn();
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ all.push(iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
+ }
+ }
}
+ all.sort(compareMeasurements);
+ return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all);
+ }
+
+ async function runBench(bench) {
const step = new BenchStep({
name: bench.name,
sanitizeExit: bench.sanitizeExit,
warmup: false,
});
- try {
- const warmupIterations = bench.warmupIterations;
- step.warmup = true;
+ let token = null;
- for (let i = 0; i < warmupIterations; i++) {
- await bench.fn(step);
+ try {
+ if (bench.permissions) {
+ token = core.opSync(
+ "op_pledge_test_permissions",
+ serializePermissions(bench.permissions),
+ );
}
- const iterations = bench.n;
- step.warmup = false;
+ const benchTimeInMs = 500;
+ const fn = bench.fn.bind(null, step);
+ const stats = await benchMeasure(benchTimeInMs, fn, step, !bench.async);
- for (let i = 0; i < iterations; i++) {
- await bench.fn(step);
- }
-
- return "ok";
+ return { ok: { stats, ...bench } };
} catch (error) {
- return {
- "failed": formatError(error),
- };
+ return { failed: { ...bench, error: formatError(error) } };
+ } finally {
+ if (token !== null) core.opSync("op_restore_test_permissions", token);
}
}
@@ -913,35 +1063,16 @@
});
}
- function reportBenchResult(description, result, elapsed) {
+ function reportBenchResult(origin, result) {
core.opSync("op_dispatch_bench_event", {
- result: [description, result, elapsed],
+ result: [origin, result],
});
}
- function reportBenchIteration(fn) {
- return async function benchIteration(step) {
- let now;
- if (!step.warmup) {
- now = benchNow();
- }
- await fn(step);
- if (!step.warmup) {
- reportIterationTime(benchNow() - now);
- }
- };
- }
-
function benchNow() {
return core.opSync("op_bench_now");
}
- function reportIterationTime(time) {
- core.opSync("op_dispatch_bench_event", {
- iterationTime: time,
- });
- }
-
async function runTests({
filter = null,
shuffle = null,
@@ -1013,32 +1144,34 @@
createTestFilter(filter),
);
+ let groups = new Set();
+ const benchmarks = ArrayPrototypeFilter(filtered, (bench) => !bench.ignore);
+
+ // make sure ungrouped benchmarks are placed above grouped
+ groups.add(undefined);
+
+ for (const bench of benchmarks) {
+ bench.group ||= undefined;
+ groups.add(bench.group);
+ }
+
+ groups = ArrayFrom(groups);
+ ArrayPrototypeSort(
+ benchmarks,
+ (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group),
+ );
+
reportBenchPlan({
origin,
- total: filtered.length,
- filteredOut: benches.length - filtered.length,
+ total: benchmarks.length,
usedOnly: only.length > 0,
+ names: ArrayPrototypeMap(benchmarks, (bench) => bench.name),
});
- for (const bench of filtered) {
- // TODO(bartlomieju): probably needs some validation?
- const iterations = bench.n ?? 1000;
- const warmupIterations = bench.warmup ?? 1000;
- const description = {
- origin,
- name: bench.name,
- iterations,
- };
- bench.n = iterations;
- bench.warmupIterations = warmupIterations;
- const earlier = DateNow();
-
- reportBenchWait(description);
-
- const result = await runBench(bench);
- const elapsed = DateNow() - earlier;
-
- reportBenchResult(description, result, elapsed);
+ for (const bench of benchmarks) {
+ bench.baseline = !!bench.baseline;
+ reportBenchWait({ origin, ...bench });
+ reportBenchResult(origin, await runBench(bench));
}
globalThis.console = originalConsole;
@@ -1420,7 +1553,7 @@
*/
function wrapBenchFnWithSanitizers(fn, opts) {
if (opts.sanitizeExit) {
- fn = assertExit(fn, false);
+ fn = opts.async ? assertExit(fn, false) : assertExitSync(fn, false);
}
return fn;
}