Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions tasks/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ import { Cell, Row, Table } from "jsr:@cliffy/[email protected]";
await main(function* (args) {
let options = parser()
.name("bench")
.description(
"Run Effection benchmarks",
)
.description("Run Effection benchmarks")
.version("0.0.0")
.options({
include: {
Expand Down Expand Up @@ -58,35 +56,45 @@ await main(function* (args) {
for (let scenario of filter(scenarios, { include, exclude })) {
tasks.push(
yield* spawn(() =>
runBenchmark(scenario, { ...options, type: "benchmark" })
runBenchmark(scenario, { ...options, type: "benchmark" }),
),
);
}

let results = yield* all(tasks);

let events = results.filter((result) => result.name.match("events"));
let startups = results.filter((result) => result.name.match("startup"));
let recursion = results.filter((result) => result.name.match("recursion"));

if (events.length == 0 && recursion.length === 0) {
console.log("no benchmarks run");
return;
}

let rows = [];
let rows: Row[] = [];

let cells = ["Library", "Avg (ms)", "Avg Startup Time (ms)"];
let cols = cells.length;

if (recursion.length > 0) {
rows.push(Row.from([new Cell("Basic Recursion").colSpan(2).border()]));
rows.push(Row.from<Cell | string>(["Library", "Avg (ms)"]).border());
rows.push(Row.from([new Cell("Basic Recursion").colSpan(cols).border()]));
rows.push(Row.from<Cell | string>(cells).border());
rows.push(...recursion.map((event) => Row.from(toTableRow(event))));
}

if (events.length > 0) {
rows.push(Row.from([new Cell("Recursive Events").colSpan(2).border()]));
rows.push(Row.from<Cell | string>(["Library", "Avg (ms)"]).border());
rows.push(Row.from([new Cell("Recursive Events").colSpan(cols).border()]));
rows.push(Row.from<Cell | string>(cells).border());
rows.push(...events.map((event) => Row.from(toTableRow(event))));
}

if (startups.length > 0) {
rows.push(Row.from([new Cell("Start-up Time").colSpan(cols).border()]));
rows.push(Row.from<Cell | string>(cells).border());
rows.push(...startups.map((event) => Row.from(toTableRow(event))));
}

Table.from(rows).render();
});

Expand Down Expand Up @@ -150,8 +158,8 @@ function filter(
function toTableRow(event: BenchmarkDoneEvent): string[] {
let [name = event.name] = event.name.split(".");
if (event.result.ok) {
let { avgTime } = event.result.value;
return [name, String(avgTime)];
let { avgTime, avgStartupTime } = event.result.value;
return [name, String(avgTime), String(avgStartupTime)];
} else {
return [name, "❌"];
}
Expand Down
2 changes: 2 additions & 0 deletions tasks/bench/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export default [
"./scenarios/rxjs.events.ts",
"./scenarios/add-event-listener.events.ts",
"./scenarios/effect.events.ts",
"./scenarios/effection.startup.ts",
"./scenarios/effect.startup.ts",
].map((mod) => import.meta.resolve(mod));
5 changes: 2 additions & 3 deletions tasks/bench/scenarios/effect.recursion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { Effect } from "npm:effect";
import { call } from "../../../mod.ts";
import { scenario } from "./scenario.ts";

await scenario(
"effect.recursion",
(depth) => call(() => Effect.runPromise(recurse(depth))),
await scenario("effect.recursion", (depth) =>
call(() => Effect.runPromise(recurse(depth)))
);

function recurse(depth: number): Effect.Effect<void, never, never> {
Expand Down
20 changes: 20 additions & 0 deletions tasks/bench/scenarios/effect.startup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Effect, Fiber } from "npm:effect";
import { call, ensure } from "../../../mod.ts";
import { scenario } from "./scenario.ts";

await scenario("effect.startup", function* (_, exit) {
let start = performance.now();

const startup = Effect.gen(function* () {
exit(performance.now() - start);
yield* Effect.promise(() => Promise.resolve());
});

const fiber = Effect.runFork(startup);

yield* ensure(function* () {
yield* call(() => Effect.runPromise(Fiber.interrupt(fiber)));
});

return yield* call(() => Effect.runPromise(Fiber.join(fiber)));
});
13 changes: 13 additions & 0 deletions tasks/bench/scenarios/effection.startup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { call, type Operation } from "../../../mod.ts";
import { scenario } from "./scenario.ts";

await scenario("effection.startup", function* (_, exit) {
let start = performance.now();

function* startup(): Operation<void> {
exit(performance.now() - start);
yield* call(() => Promise.resolve());
}

return yield* startup();
});
38 changes: 34 additions & 4 deletions tasks/bench/scenarios/scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { callcc } from "../../../lib/callcc.ts";
import { Err, Ok } from "../../../lib/result.ts";
import { encapsulate } from "../../../lib/task.ts";
import {
call,
createChannel,
each,
main,
type Operation,
race,
spawn,
withResolvers,
} from "../../../mod.ts";
import type {
BenchmarkOptions,
Expand All @@ -21,7 +24,7 @@ const send = (event: BenchmarkWorkerEvent) => self.postMessage(event);

export function scenario(
name: string,
perform: (depth: number) => Operation<void>,
perform: (depth: number, exit: (time: number) => void) => Operation<void>,
) {
return main(function* () {
try {
Expand All @@ -41,25 +44,52 @@ export function scenario(

for (let options of yield* each(work)) {
let times: number[] = [];
let entryTimes: number[] = [];

for (let i = 0; i < options.repeat; i++) {
let start = performance.now();

yield* encapsulate(() => perform(options.depth));
const exit = withResolvers<number | null>();

const task = yield* spawn(function* () {
yield* encapsulate(() => perform(options.depth, exit.resolve));
});

// Avoid blocking indefintely because not all benchmarks need the exit function
yield* race([
exit.operation,
call(function* () {
yield* task;
exit.resolve(null);
}),
]);

const entryTime = yield* exit.operation;

let time = performance.now() - start;
send({ type: "repeat", name, time, rep: i + 1 });
times.push(time);

if (entryTime) {
entryTimes.push(entryTime);
}
}

let total = times.reduce((sum, time) => sum + time, 0);
let avgTime = total / times.length;
let result = Ok({ avgTime, reps: options.repeat });

let startupTime = entryTimes.reduce((sum, time) => sum + time, 0);

let avgStartupTime =
entryTimes.length > 0 ? startupTime / entryTimes.length : 0;

let result = Ok({ avgTime, avgStartupTime, reps: options.repeat });

send({ type: "done", name, result });

yield* each.next();
}
})
}),
);
} catch (error) {
send({ type: "done", name, result: Err(error as Error) });
Expand Down
1 change: 1 addition & 0 deletions tasks/bench/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export interface BenchmarkDoneEvent {
result: Result<{
reps: number;
avgTime: number;
avgStartupTime: number;
}>;
}
Loading