The index advantage#
Build an index once and qsv can skip the opening scan on every run after. The payoff is lopsided — 5.9x for stats down to 2.3x for validate on this run, and next to nothing for streaming commands, so only the pairs where an index actually moves the needle are shown here.
see the qsv viz command
figs.append(viz("bar", index_src,
["--x", "command", "--y", "recs_per_sec", "--series", "index_status",
"--title", "The index advantage", "--y-title", "records/sec"],
"index_advantage", "The index advantage",
"Build an index once and qsv can skip the opening scan on every run after. "
f"The payoff is lopsided — {ip_span} on this run, and next to nothing for "
"streaming commands, so only the pairs where an index actually moves the "
"needle are shown here."))view on GitHub ↗ (gen_benchmark_viz.py, lines 735–742)count with an index is effectively instant#
The index advantage at its extreme. With an index, count doesn't scan at all — it reads a row count qsv already stored, a ~96x jump from ~1.0M to ~100M rows/sec. It sits on its own axis precisely because that number would flatten every other bar on the page. The un-indexed bar scans the file to count it, so it tracks whatever the current scan path costs; the indexed bar reads the stored count and does not.
see the qsv viz command
figs.append(viz("bar", prep_count(),
["--x", "index_status", "--y", "recs_per_sec",
"--title", "count: the index-read superpower", "--y-title", "records/sec"],
"count_callout", "count with an index is effectively instant",
"The index advantage at its extreme. With an index, count doesn't scan at all — "
f"it reads a row count qsv already stored, {c_jump}. It sits on its own axis "
"precisely because that number would flatten every other bar on the page. The "
"un-indexed bar scans the file to count it, so it tracks whatever the current "
"scan path costs; the indexed bar reads the stored count and does not."))view on GitHub ↗ (gen_benchmark_viz.py, lines 750–758)The index superpowers#
Some commands don't just skip the opening scan with an index — they skip almost all the work. slice and sample seek straight to the rows they need; schema reuses cached statistics. That's a different order of magnitude: slice_one_middle leads at 49x faster, with the rest of the seek-and-reuse tier tens of times over — versus the low single digits for the scan-skippers above.
see the qsv viz command
figs.append(viz("bar", sp_src,
["--x", "command", "--y", "speedup",
"--title", "The index superpowers — times faster with an index",
"--y-title", "× faster with an index"],
"index_superpowers", "The index superpowers",
"Some commands don't just skip the opening scan with an index — they skip almost "
"all the work. slice and sample seek straight to the rows they need; schema reuses "
f"cached statistics. That's a different order of magnitude: {sp_top_cmd} leads at "
f"{sp_top_x:.0f}x faster, with the rest of the seek-and-reuse tier tens of times "
"over — versus the low single digits for the scan-skippers above."))view on GitHub ↗ (gen_benchmark_viz.py, lines 761–770)sqlp tuning: the schema-cache knob#
sqlp infers a schema before it runs. Cache that schema and the re-inference cost disappears on the next query — worth about a third more throughput on these aggregations, for a one-line option.
see the qsv viz command
figs.append(viz("bar", prep_sqlp(),
["--x", "name", "--y", "recs_per_sec",
"--title", "sqlp tuning: the Polars schema-cache knob",
"--y-title", "records/sec"],
"sqlp_tuning", "sqlp tuning: the schema-cache knob",
"sqlp infers a schema before it runs. Cache that schema and the re-inference "
"cost disappears on the next query — worth about a third more throughput on "
"these aggregations, for a one-line option."))view on GitHub ↗ (gen_benchmark_viz.py, lines 771–778)The long view — every release#
Records/sec for the marquee commands across all 61 releases since 0.113.0, each shown twice: the plain scan and its indexed variant. The gap between a pair is what an index buys you. On the current release the widest gap is frequency at 4.8x and the narrowest validate at 2.3x. search and searchset only learned to use an index at 10.0.0, so those two _index lines start there. validate is the exception — its pair runs together for most of the history; an index barely helped it until the latest releases. stats is shown as the heavier --everything pass. The y-axis is logarithmic: ten lines this far apart would pile up along the bottom of a linear axis, and on a log axis a constant multiple reads as a constant vertical gap. Broad trajectory only (see the note above); count and moarstats are omitted for scale.
see the qsv viz command
figs.append(viz("line", prep_trend(),
["--x", "version", "--y", "recs_per_sec", "--series", "name",
"--title", "Throughput across every release, plain vs indexed "
f"({first_release} → {latest_release})",
"--y-title", "records/sec"],
"trend", "The long view — every release",
f"Records/sec for the marquee commands across all {n_releases} releases since "
f"{first_release}, each shown twice: the plain scan and its indexed variant. "
f"The gap between a pair is what an index buys you. {tr_gap}"
"search and searchset only learned to use an index at 10.0.0, so those two "
"_index lines start there. validate is the exception — its pair runs together "
"for most of the history; an index barely helped it until the latest releases. "
"stats is shown as the heavier --everything pass. The y-axis is logarithmic: "
"ten lines this far apart would pile up along the bottom of a linear axis, and "
"on a log axis a constant multiple reads as a constant vertical gap. Broad "
"trajectory only (see the note above); count and moarstats are omitted for "
"scale.",
log_y=True))view on GitHub ↗ (gen_benchmark_viz.py, lines 790–807)Flagship deep-dive: stats#
qsv's most-used command, indexed to its own launch speed. Over the years stats kept adding statistics — cardinality, quartiles, MAD, skewness — yet never got slower: plain stats now runs 1.9x its first-release speed, and the full --everything pass with an index 5.8x. Features grew; the curve still points up. (The odd single-release dip is a failed benchmark run, not a regression.)
see the qsv viz command
figs.append(viz("line", stats_src,
["--x", "version", "--y", "rel", "--series", "name",
"--title", "Flagship deep-dive: stats got richer AND faster",
"--y-title", "speed vs first release (1.0 = launch)"],
"stats_growth", "Flagship deep-dive: stats",
"qsv's most-used command, indexed to its own launch speed. Over the years stats "
"kept adding statistics — cardinality, quartiles, MAD, skewness — yet never got "
f"slower: plain stats now runs {s_base:.1f}x its first-release speed, and the "
f"full --everything pass with an index {s_heavy:.1f}x. Features grew; the curve "
"still points up. (The odd single-release dip is a failed benchmark run, not a "
"regression.)"))view on GitHub ↗ (gen_benchmark_viz.py, lines 818–828)Flagship deep-dive: frequency#
The same story for qsv's second flagship. As frequency gained sorted, case-insensitive and unlimited modes, throughput climbed rather than eroded — base frequency is now 1.1x its launch speed and the indexed run 2.1x. Newer modes join partway, each measured from its own debut; the transient dips are measurement artifacts, not regressions.
see the qsv viz command
figs.append(viz("line", freq_src,
["--x", "version", "--y", "rel", "--series", "name",
"--title", "Flagship deep-dive: frequency held its ground",
"--y-title", "speed vs first release (1.0 = launch)"],
"freq_growth", "Flagship deep-dive: frequency",
"The same story for qsv's second flagship. As frequency gained sorted, "
"case-insensitive and unlimited modes, throughput climbed rather than eroded — "
f"base frequency is now {f_base:.1f}x its launch speed and the indexed run "
f"{f_idx:.1f}x. Newer modes join partway, each measured from its own debut; the "
"transient dips are measurement artifacts, not regressions."))view on GitHub ↗ (gen_benchmark_viz.py, lines 829–838)Flagship deep-dive: validate#
qsv's data-quality workhorse, each variant indexed to its own launch speed. As validate gained modes — the fast no-schema structural pass, batch validation, dynamic-enum lookups, newer JSON Schema drafts — full JSON-Schema validation kept pace: validate_index now runs 2.7x its first-release speed. The no-schema structural path — the most parse-bound variant — dipped ~40% below launch from 17.0.0 through 21.0.0, when qsv's CSV parser (the csv-nose fork) moved to its 1.0.x line, then recovered sharply in 21.1.0 once csv-nose 1.1.0 landed SIMD UTF-8 validation and memchr-based scanning, and holds at 1.1x (above launch) in 23.0.1. Full-schema and no-schema paths are shown base vs index, each normalized to its own debut. (The sharp single-release spikes down — e.g. at 1.0.0 and 2.2.1 — are failed benchmark runs, not regressions.)
see the qsv viz command
figs.append(viz("line", validate_src,
["--x", "version", "--y", "rel", "--series", "name",
"--title", "Flagship deep-dive: validate",
"--y-title", "speed vs first release (1.0 = launch)"],
"validate_growth", "Flagship deep-dive: validate",
"qsv's data-quality workhorse, each variant indexed to its own launch speed. As "
"validate gained modes — the fast no-schema structural pass, batch validation, "
"dynamic-enum lookups, newer JSON Schema drafts — full JSON-Schema validation kept "
f"pace: validate_index now runs {v_idx:.1f}x its first-release speed. The no-schema "
"structural path — the most parse-bound variant — dipped ~40% below launch from "
"17.0.0 through 21.0.0, when qsv's CSV parser (the csv-nose fork) moved to its 1.0.x "
"line, then recovered sharply in 21.1.0 once csv-nose 1.1.0 landed SIMD UTF-8 "
f"validation and memchr-based scanning, and holds at {v_ns_idx:.1f}x (above launch) "
f"in {latest_release}. Full-schema "
"and no-schema paths are shown base vs index, each normalized to its own debut. (The "
"sharp single-release spikes down — e.g. at 1.0.0 and 2.2.1 — are failed benchmark "
"runs, not regressions.)"))view on GitHub ↗ (gen_benchmark_viz.py, lines 839–855)Flagship deep-dive: moarstats#
qsv's advanced-statistics command — moarstats is designed to run right AFTER stats, extending the stats CSV that stats produces. Only its bivariate variants are charted here. The two univariate variants (moarstats and moarstats --advanced) are left out because their benchmark numbers don't reflect real throughput: moarstats persists its computed columns into the .stats.csv and skips any statistic whose column is already present, so the suite's repeated timed runs short-circuit the work and report a nominal ~90M recs/sec that measures skipped work, not per-row cost. Those univariate paths do genuinely scan the original data — always for the outlier statistics, and for kurtosis, Gini/Atkinson and entropy under --advanced. The bivariate computation, by contrast, is recomputed on every run, so it carries a real throughput-over-time story. Each charted variant is indexed to its own launch speed. moarstats does real pairwise work — the default bivariate pass runs at hundreds of thousands of records/sec and the full --bivariate-stats all battery at a few thousand — so unlike stats or frequency there is genuine per-row cost to hold down. It held: the default bivariate pass now runs 7.5x its first-release speed, stepping up at 17.0.0, again at 20.0.0, and sharply in 23.0.1. The full all battery is heavier and choppier — it climbed to a ~6k-recs/sec peak around 19.x-20.x, then dipped through 21.0.0-21.1.0, before leaping to 38.5x launch once dictionary-encoding of the joint keys cut the per-pair cost of the --bivariate-stats all battery by well over an order of magnitude. All four lines are base vs --advanced, each normalized to its own debut. Note the LOG y-axis: that last jump is large enough that a linear scale would flatten every earlier step into the baseline.
see the qsv viz command
figs.append(viz("line", moarstats_src,
["--x", "version", "--y", "rel", "--series", "name",
"--title", "Flagship deep-dive: moarstats",
"--y-title", "speed vs first release (1.0 = launch)"],
"moarstats_growth", "Flagship deep-dive: moarstats",
"qsv's advanced-statistics command — moarstats is designed to run right AFTER stats, "
"extending the stats CSV that stats produces. Only its bivariate variants are charted "
"here. The two univariate variants (moarstats and moarstats --advanced) are left out "
"because their benchmark numbers don't reflect real throughput: moarstats persists its "
"computed columns into the .stats.csv and skips any statistic whose column is already "
"present, so the suite's repeated timed runs short-circuit the work and report a "
"nominal ~90M recs/sec that measures skipped work, not per-row cost. Those univariate "
"paths do genuinely scan the original data — always for the outlier statistics, and "
"for kurtosis, Gini/Atkinson and entropy under --advanced. The bivariate computation, "
"by contrast, is recomputed on every run, so it carries a real throughput-over-time "
"story. Each charted variant is indexed to its own launch speed. moarstats does real "
"pairwise work — the default bivariate pass runs at hundreds of thousands of "
"records/sec and the full --bivariate-stats all battery at a few thousand — so unlike "
f"stats or frequency there is genuine per-row cost to hold down. It held: the default "
f"bivariate pass now runs {m_biv:.1f}x its first-release speed, stepping up at 17.0.0, "
f"again at 20.0.0, and sharply in {latest_release}. The full all battery is heavier and "
"choppier — it climbed to a ~6k-recs/sec peak around 19.x-20.x, then dipped through "
f"21.0.0-21.1.0, before leaping to {m_all:.1f}x launch once dictionary-encoding of "
"the joint keys cut the per-pair cost of the --bivariate-stats all battery by well "
"over an order of magnitude. All four lines are base vs --advanced, "
"each normalized to its own debut. Note the LOG y-axis: that last jump is large enough "
"that a linear scale would flatten every earlier step into the baseline.",
log_y=True))view on GitHub ↗ (gen_benchmark_viz.py, lines 856–883)Relative throughput heatmap#
The indexed marquee commands over the recent window, each row normalized to its own peak (1.0 = that command's fastest release). Normalizing per row lets a 90M-rows/sec count and a 600k-rows/sec frequency share one canvas — the colour shows trajectory, not absolute speed.
see the qsv viz command
figs.append(viz("heatmap", prep_heatmap(hm_versions),
["--x", "version", "--y", "name", "--z", "rel",
"--title", "Relative throughput vs each command's recent peak"],
"heatmap", "Relative throughput heatmap",
"The indexed marquee commands over the recent window, each row normalized to its "
"own peak (1.0 = that command's fastest release). Normalizing per row lets a "
"90M-rows/sec count and a 600k-rows/sec frequency share one canvas — the colour "
"shows trajectory, not absolute speed."))view on GitHub ↗ (gen_benchmark_viz.py, lines 884–891)Where the suite spends its time#
The whole suite by wall-clock, family then benchmark. The biggest tiles are where a speedup would move the needle most — a map of where the optimization effort is best spent.
see the qsv viz command
figs.append(viz("treemap", prep_treemap(),
["--cols", "family,name", "--value", "mean", "--agg", "sum",
"--title", "Where the suite spends time (mean run time)"],
"time_spent", "Where the suite spends its time",
"The whole suite by wall-clock, family then benchmark. The biggest tiles are "
"where a speedup would move the needle most — a map of where the optimization "
"effort is best spent."))view on GitHub ↗ (gen_benchmark_viz.py, lines 892–898)Biggest speedups this release#
The 15 benchmarks that improved most over the previous release. Bigger, brighter bubbles are larger wins — the percentage cut in mean run time from one version to the next.
see the qsv viz command
figs.append(viz("scatter", prep_gainers(),
["--x", "name", "--y", "delta (%)", "--size", "delta (%)", "--color", "delta (%)",
"--title", "Biggest speedups this release", "--y-title", "% faster vs previous version"],
"gainers", "Biggest speedups this release",
"The 15 benchmarks that improved most over the previous release. Bigger, brighter "
"bubbles are larger wins — the percentage cut in mean run time from one version "
"to the next."))view on GitHub ↗ (gen_benchmark_viz.py, lines 899–905)Change distribution by family#
The wider view behind the speedups: the spread of per-release change within each family (above zero = faster). Most families cluster just north of zero — steady, unglamorous progress. Extreme outliers (|Δ| > 100%, usually measurement noise) are omitted; the y-axis follows the remaining data, so every point charted here is visible.
see the qsv viz command
figs.append(viz("box", delta_box_src,
["--y", "delta (%)", "--x", "family",
"--title", "Release-over-release change by command family",
"--y-title", "% faster vs previous version",
f"--y-range={d_lo - d_pad:.0f}:{d_hi + d_pad:.0f}"],
"change_by_family", "Change distribution by family",
"The wider view behind the speedups: the spread of per-release change within each "
"family (above zero = faster). Most families cluster just north of zero — steady, "
f"unglamorous progress. Extreme outliers (|Δ| > {int(DELTA_CLAMP)}%, usually "
"measurement noise) are omitted; the y-axis follows the remaining data, so every "
"point charted here is visible."))view on GitHub ↗ (gen_benchmark_viz.py, lines 912–922)