From 4f1cc5f16f75fc6854247cd87223280ba4c57c17 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 28 Oct 2026 12:07:52 +0000 Subject: [PATCH] chore(example): benchmark async reads and fractional REAL values The read benchmarks only measured synchronous queries, where native code encodes and JavaScript parses results on the same thread. They also stored only whole numbers in REAL columns, which skip the slower number formatting. Add asynchronous versions of the narrow and wide reads and a read of 100k rows with fractional REAL values. The existing cases keep their order and labels so earlier results stay comparable. Co-Authored-By: Claude Opus 5.5 --- example/src/benchmarks/readCases.ts | 206 +++++++++++++++++++++------- 1 file changed, 155 insertions(+), 51 deletions(-) diff --git a/example/src/benchmarks/readCases.ts b/example/src/benchmarks/readCases.ts index 4c7f34e1..42f93d20 100644 --- a/example/src/benchmarks/readCases.ts +++ b/example/src/benchmarks/readCases.ts @@ -9,6 +9,7 @@ import { const NARROW_ROWS = 10000 const WIDE_ROWS = 300000 +const FRACTIONAL_ROWS = 100000 const POINT_READS = 1000 const CHUNK_SIZE = 1000 const NARROW_SCHEMA = @@ -18,6 +19,35 @@ const WIDE_INSERT = 'INSERT INTO Test VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' const WIDE_SCHEMA = 'CREATE TABLE Test (id INT PRIMARY KEY, v1 TEXT, v2 TEXT, v3 TEXT, v4 TEXT, v5 TEXT, v6 INT, v7 INT, v8 INT, v9 INT, v10 INT, v11 REAL, v12 REAL, v13 REAL, v14 REAL) STRICT' +const FRACTIONAL_SCHEMA = + 'CREATE TABLE Bench (id INTEGER PRIMARY KEY, price REAL NOT NULL, ratio REAL NOT NULL, latitude REAL NOT NULL, longitude REAL NOT NULL) STRICT' +const FRACTIONAL_INSERT = 'INSERT INTO Bench VALUES (?, ?, ?, ?, ?)' + +type NarrowRow = { id: number; name: string; value: number } +type WideRow = { + id: number + v1: string + v2: string + v3: string + v4: string + v5: string + v6: number + v7: number + v8: number + v9: number + v10: number + v11: number + v12: number + v13: number + v14: number +} +type FractionalRow = { + id: number + price: number + ratio: number + latitude: number + longitude: number +} const lookupIds = Array.from( { length: POINT_READS }, @@ -38,13 +68,29 @@ export const readCases: BenchmarkCase[] = [ { id: 'read-narrow', label: '10k narrow rows · full read + access', - setup: setupNarrowRead, + setup: () => setupNarrowRead(false), }, { id: 'read-wide', label: '300k wide rows · full read + access', measuredRuns: 3, - setup: setupWideRead, + setup: () => setupWideRead(false), + }, + { + id: 'read-narrow-async', + label: '10k narrow rows · async read + access', + setup: () => setupNarrowRead(true), + }, + { + id: 'read-wide-async', + label: '300k wide rows · async read + access', + measuredRuns: 3, + setup: () => setupWideRead(true), + }, + { + id: 'read-fractional', + label: '100k fractional REAL rows · full read + access', + setup: setupFractionalRead, }, ] @@ -98,25 +144,33 @@ async function setupPointReads(prepared: boolean): Promise { } } -async function setupNarrowRead(): Promise { - const db = createBenchmarkDatabase('benchmark_narrow', NARROW_SCHEMA) +async function setupNarrowRead( + asynchronous: boolean, +): Promise { + const db = createBenchmarkDatabase( + asynchronous ? 'benchmark_narrow_async' : 'benchmark_narrow', + NARROW_SCHEMA, + ) try { await seedNarrowRows(db) let count = 0 let checksum = 0 + const access = (rows: NarrowRow[]) => { + count = rows.length + checksum = 0 + for (const row of rows) checksum += row.id + row.value + row.name.length + } return { database: db, - run: () => { - const rows = db.execute<{ - id: number - name: string - value: number - }>('SELECT * FROM Bench').rows._array - count = rows.length - checksum = 0 - for (const row of rows) checksum += row.id + row.value + row.name.length - }, + run: asynchronous + ? async () => + access( + (await db.executeAsync('SELECT * FROM Bench')).rows + ._array, + ) + : () => + access(db.execute('SELECT * FROM Bench').rows._array), verify: () => { assertBenchmark(count === NARROW_ROWS, 'Wrong narrow read row count') assertBenchmark( @@ -132,8 +186,10 @@ async function setupNarrowRead(): Promise { } } -async function setupWideRead(): Promise { - const db = open({ name: 'benchmark_wide' }) +async function setupWideRead(asynchronous: boolean): Promise { + const db = open({ + name: asynchronous ? 'benchmark_wide_async' : 'benchmark_wide', + }) try { resetLargeDbSchema(db) let expected = 0 @@ -147,52 +203,91 @@ async function setupWideRead(): Promise { await db.executeBatchAsync([{ query: WIDE_INSERT, params }]) } + let count = 0 + let checksum = 0 + const access = (rows: WideRow[]) => { + count = rows.length + checksum = 0 + for (const row of rows) { + checksum += + row.id + + row.v1.length + + row.v2.length + + row.v3.length + + row.v4.length + + row.v5.length + + row.v6 + + row.v7 + + row.v8 + + row.v9 + + row.v10 + + row.v11 + + row.v12 + + row.v13 + + row.v14 + } + } + return { + database: db, + run: asynchronous + ? async () => + access( + (await db.executeAsync('SELECT * FROM Test')).rows + ._array, + ) + : () => access(db.execute('SELECT * FROM Test').rows._array), + verify: () => { + assertBenchmark(count === WIDE_ROWS, 'Wrong wide read row count') + assertBenchmark(checksum === expected, 'Wrong wide read checksum') + }, + dispose: () => disposeBenchmarkDatabase(db), + } + } catch (error) { + disposeBenchmarkDatabase(db) + throw error + } +} + +// The wide table stores whole numbers in its REAL columns. These rows store +// fractional values, which take a slower formatting path in native code. +async function setupFractionalRead(): Promise { + const db = createBenchmarkDatabase('benchmark_fractional', FRACTIONAL_SCHEMA) + try { + let expected = 0 + for (let start = 0; start < FRACTIONAL_ROWS; start += CHUNK_SIZE) { + const params: SQLiteQueryParams[] = [] + for ( + let i = start; + i < Math.min(start + CHUNK_SIZE, FRACTIONAL_ROWS); + i++ + ) { + const [price, ratio, latitude, longitude] = fractionalRow(i) + params.push([i, price, ratio, latitude, longitude]) + expected += price + ratio + latitude + longitude + } + await db.executeBatchAsync([{ query: FRACTIONAL_INSERT, params }]) + } + let count = 0 let checksum = 0 return { database: db, run: () => { - const rows = db.execute<{ - id: number - v1: string - v2: string - v3: string - v4: string - v5: string - v6: number - v7: number - v8: number - v9: number - v10: number - v11: number - v12: number - v13: number - v14: number - }>('SELECT * FROM Test').rows._array + const rows = db.execute( + 'SELECT * FROM Bench ORDER BY id', + ).rows._array count = rows.length checksum = 0 for (const row of rows) { - checksum += - row.id + - row.v1.length + - row.v2.length + - row.v3.length + - row.v4.length + - row.v5.length + - row.v6 + - row.v7 + - row.v8 + - row.v9 + - row.v10 + - row.v11 + - row.v12 + - row.v13 + - row.v14 + checksum += row.price + row.ratio + row.latitude + row.longitude } }, verify: () => { - assertBenchmark(count === WIDE_ROWS, 'Wrong wide read row count') - assertBenchmark(checksum === expected, 'Wrong wide read checksum') + assertBenchmark( + count === FRACTIONAL_ROWS, + 'Wrong fractional read row count', + ) + assertBenchmark(checksum === expected, 'Wrong fractional read checksum') }, dispose: () => disposeBenchmarkDatabase(db), } @@ -240,3 +335,12 @@ function wideRow(i: number): SQLiteQueryParams { function wideRowChecksum(i: number): number { return 10 * i + 36 + 5 * `n${i % 1000}`.length } + +function fractionalRow(i: number): [number, number, number, number] { + return [ + (i % 100000) / 100 + 0.005, + i / 7 + 0.5, + 48.2 + i * 1e-6, + 16.37 + i * 1e-6, + ] +}