Skip to content

Commit 08ed986

Browse files
committed
refactored benchmarks
1 parent ad4be33 commit 08ed986

7 files changed

Lines changed: 655 additions & 438 deletions

File tree

.eslintrc.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module.exports = {
22
"extends": "eslint:recommended",
33
"env": {
4-
"es2017": true,
4+
"es2020": true,
55
"node": true
66
},
77
"rules": {
88
"indent": ["error", 4],
99
"linebreak-style": ["error", "unix"],
1010
"semi": ["error", "always"],
11-
"no-cond-assign": ["error", "always"],
12-
"no-inner-declarations": "off"
11+
"no-cond-assign": ["error", "always"],
12+
"no-inner-declarations": "off"
1313
}
1414
};

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
"tar": "^7.5.10"
5151
},
5252
"devDependencies": {
53-
"benchmark": "^2.1.4",
5453
"eslint": "8.56.0",
5554
"mocha": "10.2.0",
56-
"prebuild": "13.0.1"
55+
"prebuild": "13.0.1",
56+
"tinybench": "^2.9.0"
5757
},
5858
"peerDependencies": {
5959
"node-gyp": "12.x"
@@ -75,7 +75,7 @@
7575
"rebuild": "node-gyp rebuild",
7676
"upload": "prebuild --verbose --prerelease",
7777
"frozen-install": "yarn install --frozen-lockfile",
78-
"lint": "eslint lib/ test/ tools/",
78+
"lint": "eslint .eslintrc.js lib/ test/ tools/",
7979
"test": "node test/support/createdb.js && mocha -R spec --timeout 480000"
8080
},
8181
"license": "BSD-3-Clause",

tools/benchmark/README.md

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
11
# Benchmark Tools
22

3-
This directory contains benchmark scripts to measure and compare the performance of different SQLite operations in `@homeofthings/sqlite3`.
3+
This directory contains benchmark scripts to measure and compare the performance of different SQLite operations in `@homeofthings/sqlite3` using [tinybench](https://github.com/tinylibs/tinybench).
4+
5+
## Key Features
6+
7+
- **Proper setup/teardown separation**: Database creation, table creation, and data population happen in `beforeEach` (not measured)
8+
- **Only actual operations measured**: The benchmark functions contain only the INSERT/SELECT operations
9+
- **Warmup support**: tinybench includes warmup iterations to allow V8 optimization
10+
- **Statistical accuracy**: Multiple iterations with mean, variance, and margin of error
411

512
## Running Benchmarks
613

714
### Quick Start
815

9-
Install the benchmark dependencies:
16+
Install dependencies (from project root):
1017

1118
```bash
12-
cd tools/benchmark
13-
npm install
19+
yarn install
1420
```
1521

1622
Run all benchmarks:
1723

1824
```bash
19-
node run.js
25+
node tools/benchmark/run.js
2026
```
2127

22-
### Manual Execution
23-
24-
You can also run individual benchmark scripts directly:
28+
Or from the benchmark directory:
2529

2630
```bash
27-
node insert.js
28-
node select.js
31+
cd tools/benchmark
32+
node run.js
2933
```
3034

3135
## Benchmark Scripts
3236

3337
### run.js (Recommended)
3438

3539
The main runner script executes all benchmarks and provides detailed timing statistics including:
36-
-.ops/sec (operations per second)
40+
- ops/sec (operations per second)
3741
- Mean and margin of error
3842
- Sample variance
3943

@@ -42,22 +46,24 @@ The main runner script executes all benchmarks and provides detailed timing stat
4246
Measures performance of different data insertion approaches.
4347

4448
**Benchmark tests:**
45-
- `insert literal file`: Executes SQL from a file using `db.exec()`
46-
- `insert with transaction and two statements`: Uses parallelized statements within a transaction
47-
- `insert with transaction`: Uses a single statement within a transaction
48-
- `insert without transaction`: Uses a single statement without explicit transaction
49+
- `insert: literal file`: Executes SQL from a file using `db.exec()`
50+
- `insert: transaction with two statements`: Uses parallelized statements within a transaction
51+
- `insert: with transaction`: Uses a single statement within a transaction
52+
- `insert: without transaction`: Uses a single statement without explicit transaction
4953

5054
**Usage:**
5155
```bash
52-
node insert.js
56+
node tools/benchmark/run.js
5357
```
5458

55-
**Expected output (when using run.js):**
59+
**Expected output:**
5660
```
57-
insert: insert literal file x X.XX ops/sec ±X.XX% (XXX runs sampled)
58-
insert: insert with transaction and two statements x X.XX ops/sec ±X.XX% (XXX runs sampled)
59-
insert: insert with transaction x X.XX ops/sec ±X.XX% (XXX runs sampled)
60-
insert: insert without transaction x X.XX ops/sec ±X.XX% (XXX runs sampled)
61+
Task Name ops/sec Average Time (ns) Margin Samples
62+
------------------------------------------------------------------------------------------------------
63+
insert: literal file 42.34 23620025909.09 3.71% 22
64+
insert: transaction with two statements 44.45 22495146260.87 3.01% 23
65+
insert: with transaction 43.08 23212004409.09 6.05% 22
66+
insert: without transaction 44.64 22399535956.52 5.67% 23
6167
```
6268

6369
**Key findings:**
@@ -70,20 +76,22 @@ insert: insert without transaction x X.XX ops/sec ±X.XX% (XXX runs sampled)
7076
Measures performance of different data retrieval approaches.
7177

7278
**Benchmark tests:**
73-
- `db.each()`: Iterates through results row-by-row using a callback
74-
- `db.all()`: Retrieves all results into an array at once
75-
- `db.all with reset()`: Retrieves all results after resetting the database
79+
- `select: db.each()`: Iterates through results row-by-row using a callback
80+
- `select: db.all()`: Retrieves all results into an array at once
81+
- `select: db.all with statement reset()`: Retrieves all results after resetting the statement
7682

7783
**Usage:**
7884
```bash
79-
node select.js
85+
node tools/benchmark/run.js
8086
```
8187

82-
**Expected output (when using run.js):**
88+
**Expected output:**
8389
```
84-
select: db.each x X.XX ops/sec ±X.XX% (XXX runs sampled)
85-
select: db.all x X.XX ops/sec ±X.XX% (XXX runs sampled)
86-
select: db.all with reset x X.XX ops/sec ±X.XX% (XXX runs sampled)
90+
Task Name ops/sec Average Time (ns) Margin Samples
91+
------------------------------------------------------------------------------------------------------
92+
select: db.each 0.49 2057124731700.00 3.65% 10
93+
select: db.all 0.41 2457100030700.00 4.77% 10
94+
select: db.all with statement reset 0.21 4847062019200.00 1.33% 10
8795
```
8896

8997
**Key findings:**
@@ -100,26 +108,81 @@ Contains 10,000 INSERT statements wrapped in a transaction for the "insert liter
100108

101109
Contains 10,000 INSERT statements to populate the test database for the select benchmarks.
102110

111+
## Architecture
112+
113+
### Setup/Teardown Pattern
114+
115+
Each benchmark follows this pattern to ensure only the actual operation is measured:
116+
117+
```javascript
118+
{
119+
async beforeEach(ctx) {
120+
// Setup: Create database, create table, populate data
121+
// NOT measured
122+
ctx.db = new sqlite3.Database(':memory:');
123+
await promisifyRun(ctx.db, 'CREATE TABLE foo ...');
124+
},
125+
126+
async fn(ctx) {
127+
// Benchmark: Only the operation to measure
128+
// MEASURED
129+
await insertData(ctx.db);
130+
},
131+
132+
async afterEach(ctx) {
133+
// Teardown: Close database
134+
// NOT measured
135+
await promisifyClose(ctx.db);
136+
},
137+
}
138+
```
139+
140+
### Context Object
141+
142+
A context object (`ctx`) is passed between setup, benchmark, and teardown:
143+
- `ctx.db`: Database instance
144+
- Custom properties can be added as needed
145+
103146
## Adding New Benchmarks
104147

105148
1. Create a new JavaScript file in this directory (e.g., `update.js`)
106-
2. Export a `compare` object with your benchmark functions:
149+
2. Export a `benchmarks` object with your benchmark functions:
107150

108151
```javascript
109-
exports.compare = {
110-
'benchmark name': function(finished) {
111-
// Your benchmark code here
112-
finished(); // Call when done
113-
}
152+
module.exports = {
153+
benchmarks: {
154+
'benchmark name': {
155+
async beforeEach(ctx) {
156+
// Setup - NOT measured
157+
ctx.db = new sqlite3.Database(':memory:');
158+
// ... create tables, populate data
159+
},
160+
161+
async fn(ctx) {
162+
// Benchmark - MEASURED
163+
// ... your operation here
164+
},
165+
166+
async afterEach(ctx) {
167+
// Teardown - NOT measured
168+
await promisifyClose(ctx.db);
169+
},
170+
},
171+
},
114172
};
115173
```
116174

117-
3. The `run.js` script will automatically include your new benchmarks
175+
3. Import and add your benchmarks in `run.js`
118176
4. Add documentation for your benchmark in this README
119177

120178
## Notes
121179

122180
- Benchmarks run in-memory (`:memory:`) by default for faster execution
123181
- Results may vary based on your hardware and system configuration
124-
- The `benchmark` library automatically determines the number of runs needed for statistical accuracy
182+
- tinybench automatically determines the number of runs needed for statistical accuracy
183+
- Warmup iterations allow V8 to optimize the code before measurement
125184
- For meaningful comparisons, run benchmarks multiple times and compare the results
185+
186+
## Dependencies
187+
188+
- `tinybench`: ^2.9.0 - Modern benchmark library with proper setup/teardown support

0 commit comments

Comments
 (0)