Benchmark
The same query over the same CSV file, run by five tools and timed from process start to exit with himorime. Before timing, every tool runs the query once and the outputs are compared byte for byte, so every bar below is the same answer.
What the tools are #
| Tool | Engine | Needs cgo |
|---|---|---|
| sqly | SQLite, as pure Go (modernc.org/sqlite), loaded by filesql | no |
| trdsql | SQLite through cgo | yes |
| csvq | its own engine, in Go | no |
| textql | SQLite through cgo; no release since 2015 | yes |
| DuckDB | a columnar engine in C++ that reads the file in parallel | yes (C++) |
sqly, trdsql and textql load every row into SQLite before the query runs, so their cost is mostly the load. sqly and trdsql differ in how that SQLite is built: compiled from C for trdsql, translated into Go for sqly. In return sqly is a single static binary for every platform Go builds for, with no C toolchain needed. DuckDB does not load the file into a table first; it scans only the columns the query needs, on every core, which is why it leads each benchmark by a wide margin.
textql is left out of the export benchmark: it reads the phone number
0389689232 as the number 389689232 and writes that back, so its output
differs from the other four.
Results #
aggregate 100k
The top 10 countries by row count of the 100 000 customers in testdata/benchmark/customers100000.csv (12 columns, 17 MB), printed as CSV. Median of 10 runs.
| Tool | Median | P95 | Relative to sqly | CPU time | Peak memory |
|---|---|---|---|---|---|
| sqly | 277 ms | 302 ms | 1.00x | 438 ms | 72 MiB |
| trdsql | 220 ms | 238 ms | 0.79x | 264 ms | 22 MiB |
| csvq | 193 ms | 197 ms | 0.70x | 629 ms | 189 MiB |
| textql | 294 ms | 306 ms | 1.06x | 343 ms | 33 MiB |
| duckdb | 65 ms | 70 ms | 0.23x | 90 ms | 48 MiB |
filter 100k
The e-mail address and city of every customer in Japan, sorted by address: a scan of all 100 000 rows that keeps 418 of them. Median of 10 runs.
| Tool | Median | P95 | Relative to sqly | CPU time | Peak memory |
|---|---|---|---|---|---|
| sqly | 247 ms | 255 ms | 1.00x | 407 ms | 69 MiB |
| trdsql | 196 ms | 219 ms | 0.79x | 239 ms | 20 MiB |
| csvq | 175 ms | 196 ms | 0.71x | 488 ms | 158 MiB |
| textql | 271 ms | 297 ms | 1.10x | 318 ms | 31 MiB |
| duckdb | 64 ms | 68 ms | 0.26x | 87 ms | 45 MiB |
export 100k
Every row and column of the customers file written back out as CSV, so reading every value and writing every value is the whole cost. textql is left out: it reads the phone number 0389689232 as the number 389689232 and writes that, so its output differs from the other four. Median of 10 runs.
| Tool | Median | P95 | Relative to sqly | CPU time | Peak memory |
|---|---|---|---|---|---|
| sqly | 461 ms | 479 ms | 1.00x | 653 ms | 77 MiB |
| trdsql | 347 ms | 357 ms | 0.75x | 403 ms | 20 MiB |
| csvq | 206 ms | 221 ms | 0.45x | 462 ms | 138 MiB |
| duckdb | 94 ms | 98 ms | 0.20x | 158 ms | 89 MiB |
aggregate 1m
The row count and the sum of one column of a generated CSV of 1 000 000 rows (4 columns, 38 MB), where loading the file is nearly the whole cost. Median of 5 runs.
| Tool | Median | P95 | Relative to sqly | CPU time | Peak memory |
|---|---|---|---|---|---|
| sqly | 970 ms | 979 ms | 1.00x | 1.64 s | 112 MiB |
| trdsql | 654 ms | 690 ms | 0.67x | 783 ms | 20 MiB |
| csvq | 727 ms | 742 ms | 0.75x | 2.07 s | 678 MiB |
| textql | 1.20 s | 1.27 s | 1.24x | 1.35 s | 49 MiB |
| duckdb | 43 ms | 48 ms | 0.04x | 82 ms | 64 MiB |
CPU time is the user plus system time of the process and every child it waited for; more than the wall time means more than one core was busy. Peak memory is the largest resident set of any one process. Each tool gets two warm-up runs, then every round runs the tools in a shuffled order, so a busy moment on the machine lands on all of them.
Measured with himorime v0.5.1 on linux/amd64, AMD RYZEN AI MAX+ 395 w/ Radeon 8060S (32 logical CPUs), 2026-09-28. sqly was built from commit cf4bf82be38c.
- trdsql: github.com/noborus/trdsql v1.2.3
- csvq: csvq version 1.18.1
- textql: github.com/dinedal/textql v0.0.0-20151217051953-1785cd353c68
- duckdb: v1.5.5 (Variegata) d8cdaa33fd
Measuring it yourself #
The suite is bench/compare/himorime.yaml.
With trdsql, csvq, textql and duckdb on PATH, make bench-docs runs it and
writes website/data/benchmark.json, which this page’s chart and tables are
drawn from. bench/README.md
pins the versions measured. Numbers from different machines are not comparable; compare tools on one
machine, as this page does.