Skip to content

Commit

Permalink
feat(js): add benchmark support
Browse files Browse the repository at this point in the history
  • Loading branch information
timkurvers committed Dec 1, 2023
1 parent e445f0f commit 9c1b69d
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 15 deletions.
4 changes: 2 additions & 2 deletions js/src/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import minimist from 'minimist';
import { Challenge } from './utils';

const args = minimist(process.argv.slice(2));
const exitCode = args['exit-code'];
const { benchmark, 'exit-code': exitCode } = args;
const requested = args._.map(Number);

const isYear = (nr) => nr > 1000;
Expand Down Expand Up @@ -45,7 +45,7 @@ const isYear = (nr) => nr > 1000;
// Find challenge for this requested year and day
const challenge = challenges.find((c) => c.day === nr && c.year === year);
if (challenge) {
await challenge.run();
await challenge.run({ benchmark });
} else {
throw new Error(`Could not find year ${year} day ${nr}`);
}
Expand Down
59 changes: 46 additions & 13 deletions js/src/utils/challenges/Challenge.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import YAML from 'yaml';
import colors from 'colors';
import globby from 'globby';

import { camelcase, time, titleize } from '..';
import {
camelcase, sum, time, titleize,
} from '..';

const SRC_ROOT = 'src';
const PUZZLE_ROOT = '../puzzles';
Expand Down Expand Up @@ -45,7 +47,7 @@ class Challenge {
return import(path.resolve(this.path));
}

async run() {
async run({ benchmark = false } = {}) {
const parts = await this.parts();
const { input: puzzleInput, examples } = this;

Expand All @@ -55,30 +57,61 @@ class Challenge {
solution,
puzzleInput,
examples: examples[part],
benchmark,
});
}
}

async runPart({
part, solution, puzzleInput, examples = [],
part, solution, puzzleInput, examples = [], benchmark,
} = {}) {
const heading = `${this.year} · Day ${this.day} · ${titleize(part)}`;
console.log(colors.cyan(heading));

const formatDuration = (duration) => {
if (!duration) return '';
if (duration < 1) return `${Math.ceil(duration * 1000)}μs`;
return `${Math.ceil(duration)}ms`;
};

// Executes and times the solution (used for both puzzle input and examples)
const iterations = benchmark ? 1000 : 1;
const execute = async (input, args = {}) => {
const [duration, answer] = await time(() => solution(input, args));
return { answer, duration };
const runs = [];
for (let i = 0; i < iterations; ++i) {
const [duration, answer] = await time(() => solution(input, args));
runs.push({ duration, answer });
}

// Only care about answer from the first run
const { duration, answer } = runs[0];

// Calculate benchmark stats
let bench;
if (iterations > 1) {
const durations = runs.map((run) => run.duration);
bench = {
avg: sum(durations) / iterations,
min: Math.min(...durations),
max: Math.max(...durations),
first: duration,
};
}

return { answer, duration, bench };
};

// Generic line rendering utility
const line = (label, text, correct, duration = null) => {
const line = (label, text, correct, duration = null, bench = null) => {
const colored = correct ? colors.green(text) : colors.red(text);
let suffix = '';
if (duration && duration < 1) {
suffix = ` ${colors.gray(`${Math.ceil(duration * 1000)}μs`)}`;
if (bench) {
const results = Object.entries(bench).map(([k, v]) => (
`${k}: ${formatDuration(v).padStart(5, ' ')}`
));
suffix += `\n ${colors.gray(`{ ${results.join(', ')} }`)}`;
} else if (duration) {
suffix = ` ${colors.gray(`${Math.ceil(duration)}ms`)}`;
suffix += ` ${colors.gray(formatDuration(duration))}`;
}
console.log(` => ${label}: ${colored}${suffix}`);
};
Expand All @@ -92,7 +125,7 @@ class Challenge {
continue;
}

const { answer, duration } = await execute(
const { answer, duration, bench } = await execute(
input, example.args ? camelcaseKeysFor(example.args) : {},
);

Expand All @@ -101,7 +134,7 @@ class Challenge {
line(`Example ${colors.yellow(excerpt)}`, '[not yet solved]');
} else {
const text = passed ? answer : `${answer} (expected: ${expected})`;
line(`Example ${colors.yellow(excerpt)}`, text, passed, duration);
line(`Example ${colors.yellow(excerpt)}`, text, passed, duration, bench);
}
if (!passed) {
console.log();
Expand All @@ -121,13 +154,13 @@ class Challenge {
return;
}

const { answer, duration } = await execute(puzzleInput);
const { answer, duration, bench } = await execute(puzzleInput);
if (answer == null) {
line('Answer', '[not yet solved]');
console.log();
return;
}
line('Answer', answer, true, duration);
line('Answer', answer, true, duration, bench);
console.log();
}

Expand Down

0 comments on commit 9c1b69d

Please sign in to comment.