| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import { createComputed, createRoot } from "solid-js"
- import { createStore, reconcile } from "solid-js/store"
- import { Keyed } from "../src"
- import { createHarness, type Workload } from "./harness"
- type Item = {
- readonly id: number
- readonly value: number
- }
- const bench = createHarness()
- const results: Array<{ readonly name: string; readonly ratio: number }> = []
- function initial(size: number) {
- return Array.from({ length: size }, (_, id): Item => ({ id, value: 0 }))
- }
- function project(values: readonly Item[]) {
- return values.reduce((total, value) => total + value.id + value.value, 0)
- }
- function quarkDirect(size: number, aggregate: boolean): Workload {
- const values = initial(size)
- const keyed = Keyed.make<Item, number>({
- key: (item) => item.id,
- equivalent: (left, right) => left.value === right.value,
- })
- keyed.set(values)
- const target = keyed.slots()[Math.floor(size / 2)]
- let sink = aggregate ? project(keyed.values()) : target().value
- const dispose = aggregate
- ? keyed.values.subscribe((next) => (sink = project(next)))
- : target.subscribe((value) => (sink = value.value))
- return {
- run: (index) => keyed.update({ id: Math.floor(size / 2), value: index + 1 }),
- consume: () => sink,
- dispose,
- }
- }
- function solidDirect(size: number, aggregate: boolean): Workload {
- let run = (_index: number) => {}
- let consume = () => 0
- let dispose = () => {}
- createRoot((rootDispose) => {
- dispose = rootDispose
- const [values, setValues] = createStore(initial(size))
- const target = Math.floor(size / 2)
- let sink = aggregate ? project(values) : values[target].value
- if (aggregate) createComputed(() => (sink = project(values)))
- else createComputed(() => (sink = values[target].value))
- run = (index) => setValues(target, reconcile({ id: target, value: index + 1 }))
- consume = () => sink
- })
- return { run, consume, dispose }
- }
- function quarkNoSubscriber(size: number): Workload {
- const keyed = Keyed.make<Item, number>({
- key: (item) => item.id,
- equivalent: (left, right) => left.value === right.value,
- })
- keyed.set(initial(size))
- const target = Math.floor(size / 2)
- return {
- run: (index) => keyed.update({ id: target, value: index + 1 }),
- consume: () => keyed.slots()[target]().value,
- }
- }
- function solidNoSubscriber(size: number): Workload {
- const [values, setValues] = createStore(initial(size))
- const target = Math.floor(size / 2)
- return {
- run: (index) => setValues(target, reconcile({ id: target, value: index + 1 })),
- consume: () => values[target].value,
- }
- }
- function solidPathWriteNoSubscriber(size: number): Workload {
- const [values, setValues] = createStore(initial(size))
- const target = Math.floor(size / 2)
- return {
- run: (index) => setValues(target, "value", index + 1),
- consume: () => values[target].value,
- }
- }
- function quarkDense(size: number): Workload {
- const keyed = Keyed.make<Item, number>({
- key: (item) => item.id,
- equivalent: (left, right) => left.value === right.value,
- })
- keyed.set(initial(size))
- let sink = project(keyed.values())
- const dispose = keyed.values.subscribe((values) => (sink = project(values)))
- return {
- run: (index) => keyed.set(initial(size).map((item) => ({ ...item, value: index + 1 }))),
- consume: () => sink,
- dispose,
- }
- }
- function solidDense(size: number): Workload {
- let run = (_index: number) => {}
- let consume = () => 0
- let dispose = () => {}
- createRoot((rootDispose) => {
- dispose = rootDispose
- const [values, setValues] = createStore(initial(size))
- let sink = project(values)
- createComputed(() => (sink = project(values)))
- run = (index) => setValues(reconcile(initial(size).map((item) => ({ ...item, value: index + 1 }))))
- consume = () => sink
- })
- return { run, consume, dispose }
- }
- function quarkUnstable(size: number): Workload {
- const keyed = Keyed.make<Item, number>({ key: (item) => item.id })
- keyed.set(initial(size))
- let sink = project(keyed.values())
- const dispose = keyed.values.subscribe((values) => (sink = project(values)))
- return {
- run(index) {
- const offset = (index + 1) * size
- keyed.set(initial(size).map((item) => ({ id: item.id + offset, value: index })))
- },
- consume: () => sink,
- dispose,
- }
- }
- function solidUnstable(size: number): Workload {
- let run = (_index: number) => {}
- let consume = () => 0
- let dispose = () => {}
- createRoot((rootDispose) => {
- dispose = rootDispose
- const [values, setValues] = createStore(initial(size))
- let sink = project(values)
- createComputed(() => (sink = project(values)))
- run = (index) => {
- const offset = (index + 1) * size
- setValues(reconcile(initial(size).map((item) => ({ id: item.id + offset, value: index }))))
- }
- consume = () => sink
- })
- return { run, consume, dispose }
- }
- function compare(name: string, iterations: number, quark: () => Workload, solid: () => Workload) {
- console.log(`\n${name}`)
- const result = bench.compare(iterations, [
- { name: `Quark ${name}`, make: quark },
- { name: `Solid ${name}`, make: solid },
- ])
- results.push({ name, ratio: result.ratio(0, 1) })
- }
- console.log(`Keyed integration benchmark (${bench.samples} samples)`)
- compare(
- "direct no subscribers 1000",
- 200_000,
- () => quarkNoSubscriber(1_000),
- () => solidNoSubscriber(1_000),
- )
- compare(
- "adversarial direct path write 1000",
- 200_000,
- () => quarkNoSubscriber(1_000),
- () => solidPathWriteNoSubscriber(1_000),
- )
- compare(
- "subscribed values 10",
- 100_000,
- () => quarkDirect(10, true),
- () => solidDirect(10, true),
- )
- compare(
- "subscribed values 100",
- 25_000,
- () => quarkDirect(100, true),
- () => solidDirect(100, true),
- )
- compare(
- "subscribed values 1000",
- 2_500,
- () => quarkDirect(1_000, true),
- () => solidDirect(1_000, true),
- )
- compare(
- "subscribed values 10000",
- 250,
- () => quarkDirect(10_000, true),
- () => solidDirect(10_000, true),
- )
- compare(
- "dense update 1000",
- 250,
- () => quarkDense(1_000),
- () => solidDense(1_000),
- )
- compare(
- "unstable keys 100",
- 1_000,
- () => quarkUnstable(100),
- () => solidUnstable(100),
- )
- console.log("\nRatios to Solid (lower is faster)")
- results.forEach((result) => {
- console.log(`${result.name.padEnd(34)} ${result.ratio.toFixed(3)}x`)
- console.log(`METRIC ${result.name.replaceAll(/[^a-z0-9]+/g, "_")}_ratio=${result.ratio.toFixed(6)}`)
- })
- bench.finish()
|