Appendix G — Where the speed lives

A benchmark of god against another tool is really a benchmark of god’s engine against that tool, because god’s own work ends when the query is written. So this page refuses to blur the two. The half that belongs to god is measured live, while this page is built. The half that belongs to the engines is a dated record, run once on real data with the date printed beside it. A speed table without its date is a rumor.

G.1 The grammar’s toll, clocked on this render

What god costs is compiling a pipeline: reading the sentence, checking it against the columns, writing the query. That cost is fixed per pipeline rather than per row, and rather than assert it, this page measures it while rendering, on a five-step sentence:

sentence <- paste(
  'sales then keep where [revenue] > 100',
  'then add [margin] as ([revenue] - [cost])',
  'then summarize [margin] as total([margin]) by [region]',
  'then sort [margin] descending then take 5')
columns <- "region:text,product:text,quantity:number,revenue:number,cost:number"

timings <- vapply(1:50, function(i) {
  started <- Sys.time()
  god_sql(sentence, columns)
  as.numeric(Sys.time() - started)
}, 0)

cat(sprintf("%.1f milliseconds per pipeline, on the machine that built this page",
            median(timings) * 1000))
2.0 milliseconds per pipeline, on the machine that built this page

A hundred rows and a hundred million compile in the same time, because the compiler reads the sentence and the column names, never the data. Everything after that belongs to the engine.

G.2 The engines’ race, on record

The record below was measured on 2026-08-10: six operations on twenty million real rows of New York taxi trips. They run from a simple filter-and-group to a seven-step capstone that filters, derives, joins, groups, ranks within each borough and takes the top two. Seven implementations were run, and every answer was proven identical across all seven before any time was read. A benchmark whose answers are not first proven equal measures nothing. The numbers are read from the measurement files, not typed, so this table cannot drift from what was measured.

Median seconds over 20,332,093 rows, 2026-08-10. macOS arm64, 10 cores, 16 GB.
The operation god (R) god (Python) dplyr data.table pandas polars, eager polars, lazy
filter three ways, then group by payment type 0.13 0.74 0.59 0.46 0.65 0.18 0.06
group by zone, hour and date: 1.2 million groups, top 5 0.15 0.38 3.56 0.34 0.50 1.45 0.29
derive tip share, median by passenger count 0.43 0.68 0.63 0.63 0.41 2.20 0.17
join the zones table, revenue by borough 0.11 1.15 1.34 1.62 1.12 2.65 0.31
top-3 tips per zone: sort, then take 3 by zone 3.06 5.68 2.22 0.70 5.89 6.21 2.08
filter, derive, join, group, rank within borough, top 2 0.22 1.63 2.12 1.93 2.53 2.15 0.29
all six together 4.09 10.27 10.47 5.68 11.10 14.84 3.19
What Detail
Versions duckdb 1.5.5, dplyr 1.2.1, data.table 1.18.4, pandas 3.0.5, polars 1.43.2, god 0.0.1
Threads, as shipped duckdb on 10, polars on 10, data.table on 5, pandas on 1, dplyr on 1
Method median of 3 timed runs after one warmup; loads excluded

G.3 What the table says, read honestly

The engine is the story, not the language. The fastest columns above are god through DuckDB and polars run lazily, one reached from R and text, one from Python. The widest gap in the whole table is not between the two languages people argue about. pandas and dplyr finish within a second of each other, and R’s own data.table beats both. Whoever hands their rows to a columnar engine wins, from either language. The folk ranking of languages is a decade out of date; the ranking that holds is a ranking of engines.

Both spellings of polars are shown on purpose. The eager style people type interactively and the lazy style that lets the planner fuse steps differ by more than four times here. A table that showed only one of them would be marketing.

god’s two columns are one engine. The same sentences, the same DuckDB, identical answers; the R side hands a frame across the boundary more cheaply than the Python side, and that boundary is the whole difference. What the grammar itself adds is the toll clocked live above, in either language.

And the number that matters most is not a time. Seven implementations, six operations, twenty million rows, identical answers, checked before the clock was trusted. Speed varies by machine and by release; that agreement is the part this book stakes its name on.

The record ages, and it says so: the date sits in the caption, the libraries move, and a number this page shows is a photograph rather than a law. When you want today’s number for your own engine, ask show_as for the exact query this grammar writes, and measure it where your data lives.