Skip to content

Commit

Permalink
feat: some improvements (#56)
Browse files Browse the repository at this point in the history
* some improvement

* throwing an error

* pass task to table
  • Loading branch information
sirenkovladd authored Jan 17, 2024
1 parent 021605e commit 2d02e67
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 116 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ console.table(bench.table());
// └─────────┴───────────────┴──────────┴────────────────────┴───────────┴─────────┘

console.table(
bench.todos.map(({ name }) => ({
'Task name': name,
})),
bench.table((task) => {'Task name': task.name})
);

// Output:
Expand Down
25 changes: 14 additions & 11 deletions src/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,29 +142,32 @@ export default class Bench extends EventTarget {
listener: T,
options?: AddEventListenerOptionsArgument,
): void {
super.addEventListener(type as string, listener as any, options);
super.addEventListener(type, listener as any, options);
}

removeEventListener<K extends BenchEvents, T = BenchEventsMap[K]>(
type: K,
listener: T,
options?: RemoveEventListenerOptionsArgument,
) {
super.removeEventListener(type as string, listener as any, options);
super.removeEventListener(type, listener as any, options);
}

/**
* table of the tasks results
*/
table() {
return this.tasks.map(({ name, result }) => {
if (result) {
return {
'Task Name': name,
'ops/sec': result.error ? 'NaN' : parseInt(result.hz.toString(), 10).toLocaleString(),
'Average Time (ns)': result.error ? 'NaN' : result.mean * 1000 * 1000,
Margin: result.error ? 'NaN' : `\xb1${result.rme.toFixed(2)}%`,
Samples: result.error ? 'NaN' : result.samples.length,
table(convert?: (task: Task) => Record<string, string | number> | undefined) {
return this.tasks.map((task) => {
if (task.result) {
if (task.result.error) {
throw task.result.error;
}
return convert?.(task) || {
'Task Name': task.name,
'ops/sec': task.result.error ? 'NaN' : parseInt(task.result.hz.toString(), 10).toLocaleString(),
'Average Time (ns)': task.result.error ? 'NaN' : task.result.mean * 1000 * 1000,
Margin: task.result.error ? 'NaN' : `\xb1${task.result.rme.toFixed(2)}%`,
Samples: task.result.error ? 'NaN' : task.result.samples.length,
};
}
return null;
Expand Down
14 changes: 8 additions & 6 deletions src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ function createBenchEvent(
target: Task | null = null,
) {
const event = new Event(eventType);
Object.defineProperty(event, 'task', {
value: target,
enumerable: true,
writable: false,
configurable: false,
});
if (target) {
Object.defineProperty(event, 'task', {
value: target,
enumerable: true,
writable: false,
configurable: false,
});
}
return event;
}

Expand Down
138 changes: 47 additions & 91 deletions src/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Bench from './bench';
import tTable from './constants';
import { createBenchEvent } from './event';
import { AddEventListenerOptionsArgument, RemoveEventListenerOptionsArgument } from './types';
import { getMean, getVariance, isAsyncTask } from './utils';
import { getVariance, isAsyncTask } from './utils';

/**
* A class that represents each benchmark task in Tinybench. It keeps track of the
Expand Down Expand Up @@ -51,28 +51,21 @@ export default class Task extends EventTarget {
// TODO: support signals in Tasks
}

/**
* run the current task and write the results in `Task.result` object
*/
async run() {
this.dispatchEvent(createBenchEvent('start', this));
private async loop(time: number, iterations: number): Promise<{ error?: unknown, samples?: number[] }> {
let totalTime = 0; // ms
const samples: number[] = [];

await this.bench.setup(this, 'run');

if (this.opts.beforeAll != null) {
try {
await this.opts.beforeAll.call(this);
} catch (e) {
this.setResult({ error: e });
} catch (error) {
return { error };
}
}
const isAsync = await isAsyncTask(this);

try {
while (
(totalTime < this.bench.time || this.runs < this.bench.iterations)
(totalTime < time || samples.length < iterations)
&& !this.bench.signal?.aborted
) {
if (this.opts.beforeEach != null) {
Expand All @@ -91,41 +84,51 @@ export default class Task extends EventTarget {
}

samples.push(taskTime);
this.runs += 1;
totalTime += taskTime;

if (this.opts.afterEach != null) {
await this.opts.afterEach.call(this);
}
}
} catch (e) {
this.setResult({ error: e });
if (this.bench.throws) {
throw e;
}
} catch (error) {
return { error };
}

if (this.opts.afterAll != null) {
try {
await this.opts.afterAll.call(this);
} catch (e) {
this.setResult({ error: e });
} catch (error) {
return { error };
}
}
return { samples };
}

await this.bench.teardown(this, 'run');
/**
* run the current task and write the results in `Task.result` object
*/
async run() {
if (this.result?.error) {
return this;
}
this.dispatchEvent(createBenchEvent('start', this));
await this.bench.setup(this, 'run');
const { samples, error } = await this.loop(this.bench.time, this.bench.iterations);
this.bench.teardown(this, 'run');

if (!this.result?.error) {
samples.sort((a, b) => a - b);
if (samples) {
const totalTime = samples.reduce((a, b) => a + b, 0);
this.runs = samples.length;

samples.sort((a, b) => a - b);
const period = totalTime / this.runs;
const hz = 1000 / period;
const samplesLength = samples.length;
const df = samplesLength - 1;
const min = samples[0]!;
const max = samples[df]!;
// benchmark.js: https://github.com/bestiejs/benchmark.js/blob/42f3b732bac3640eddb3ae5f50e445f3141016fd/benchmark.js#L1912-L1927
const mean = getMean(samples);
const mean = totalTime / samples.length || 0;
const variance = getVariance(samples, mean);
const sd = Math.sqrt(variance);
const sem = sd / Math.sqrt(samplesLength);
Expand Down Expand Up @@ -165,89 +168,42 @@ export default class Task extends EventTarget {
});
}

// eslint-disable-next-line no-lone-blocks
{
if (this.result?.error) {
this.dispatchEvent(createBenchEvent('error', this));
this.bench.dispatchEvent(createBenchEvent('error', this));
if (error) {
this.setResult({ error });
if (this.bench.throws) {
throw error;
}

this.dispatchEvent(createBenchEvent('cycle', this));
this.bench.dispatchEvent(createBenchEvent('cycle', this));
// cycle and complete are equal in Task
this.dispatchEvent(createBenchEvent('complete', this));
this.dispatchEvent(createBenchEvent('error', this));
this.bench.dispatchEvent(createBenchEvent('error', this));
}

this.dispatchEvent(createBenchEvent('cycle', this));
this.bench.dispatchEvent(createBenchEvent('cycle', this));
// cycle and complete are equal in Task
this.dispatchEvent(createBenchEvent('complete', this));

return this;
}

/**
* warmup the current task
*/
async warmup() {
if (this.result?.error) {
return;
}
this.dispatchEvent(createBenchEvent('warmup', this));
const startTime = this.bench.now();
let totalTime = 0;

await this.bench.setup(this, 'warmup');
const { error } = await this.loop(this.bench.warmupTime, this.bench.warmupIterations);
this.bench.teardown(this, 'warmup');

if (this.opts.beforeAll != null) {
try {
await this.opts.beforeAll.call(this);
} catch (e) {
this.setResult({ error: e });
}
}
const isAsync = await isAsyncTask(this);

while (
(totalTime < this.bench.warmupTime
|| this.runs < this.bench.warmupIterations)
&& !this.bench.signal?.aborted
) {
if (this.opts.beforeEach != null) {
try {
await this.opts.beforeEach.call(this);
} catch (e) {
this.setResult({ error: e });
}
}

try {
// eslint-disable-next-line no-await-in-loop
if (isAsync) {
await this.fn.call(this);
} else {
this.fn.call(this);
}
} catch (e) {
if (this.bench.throws) {
throw e;
}
}

this.runs += 1;
totalTime = this.bench.now() - startTime;

if (this.opts.afterEach != null) {
try {
await this.opts.afterEach.call(this);
} catch (e) {
this.setResult({ error: e });
}
}
}

if (this.opts.afterAll != null) {
try {
await this.opts.afterAll.call(this);
} catch (e) {
this.setResult({ error: e });
if (error) {
this.setResult({ error });
if (this.bench.throws) {
throw error;
}
}
this.bench.teardown(this, 'warmup');

this.runs = 0;
}

addEventListener<K extends TaskEvents, T = TaskEventsMap[K]>(
Expand Down
5 changes: 0 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ function isPromiseLike<T>(maybePromiseLike: any): maybePromiseLike is PromiseLik
);
}

/**
* Computes the arithmetic mean of a sample.
*/
export const getMean = (samples: number[]) => samples.reduce((sum, n) => sum + n, 0) / samples.length || 0;

/**
* Computes the variance of a sample.
*/
Expand Down

0 comments on commit 2d02e67

Please sign in to comment.