Skip to content

defk-9p6.30: tape_csv, CSV to rows through a kernel tape; the cleaned copy is 2.1-2.2x the best Elixir - #75

Merged
DanielLuning merged 4 commits into
mainfrom
defk-9p6.30/tape-csv
Oct 20, 2026
Merged

DanielLuning merged 4 commits into
mainfrom
defk-9p6.30/tape-csv

Conversation

@DanielLuning

Copy link
Copy Markdown
Owner

Merge #74 first. This branch builds on defk-9p6.21/numeric-loader (the same README rows, scoreboard and gitleaks pin).

What changed

  • New example examples/tape_csv.exs: CSV to the rows NimbleCSV.RFC4180.parse_string/2 returns, through a kernel. Two tape forms:
    • scan/2: an [i64] tape of field events, which Elixir walks with binary_part/3.
    • normalize/1: a cleaned copy (byte 31 between fields, byte 30 after rows, quotes removed). Two :binary.split/3 passes turn it into rows. This is the faster form and the verdict's defk side.
  • Elixir variants: E1 NimbleCSV; E2 :binary.split lines and fields, with quoted lines walked by :binary.match (the baseline); E3 quoted pieces joined (no gain). Native: rusty_csv 0.4.6 (precompiled Rust NIF).
  • Every timed parse runs in a fresh process. In one long-lived process the verdict moved from 0.81x to 2.08x run to run, with the heap the earlier runs left. A fresh process repeats to 2%.
  • The wrapper table's parser row: reopened narrowly for CSV to rows; rusty_csv stays the fastest; JSON to terms is not measured. Also _tables.exs, the READMEs (regenerated), and docs/performance.md 7.11.
  • examples/scoreboard.json: a quiet row at 71e2977 (--fresh from a clean tree).

Results

20 MB, a 200k-row export with quoted fields. Four runs; the stored one is quiet, the others are provisional.

ms
E1 NimbleCSV 308-315
E2 (baseline) 274-299
defk cleaned copy 130.7-141.9 (2.09-2.23x, good)
defk [i64] tape 152-158
rusty_csv 49.5-53.5 (2.6-2.7x the copy)
  • Pre-registered marginal; measured good.
  • A NIF builds sub-binaries far cheaper than Elixir does, so the old line "a NIF builds terms at the same price" is gone.
  • Sweep: 2k rows 1.6-1.7x, 20k rows 1.6-1.7x, 1M rows 2.4-2.5x.

How it was checked

  • mix ci green in the worktree (2305 passed).

  • Agreement: both tape forms, their Elixir mode, E2, E3 and rusty_csv return exactly NimbleCSV's rows on 49 inputs. 6 bad inputs raise on every side.

  • Touches a promised surface (kernel subset, runtime contract, public API, error codes)? Then a CHANGELOG.md entry, and a deprecation for a rename or removal (docs/guides/stability.md).

  • Touches test/stability/? A frozen file is never edited; a release adds a directory (docs/agents/stability.md).

…rapper row stays closed

Two tape forms against NimbleCSV.RFC4180 and rusty_csv 0.4.6: an [i64]
tape of field events walked with binary_part, and a cleaned copy (byte 30
and 31 separators) split twice by :binary.split. The copy is faster, and
still 0.81-0.93x NimbleCSV on a 20 MB export: the scan takes 28-36 ms,
building the rows in Elixir 135-314 ms. rusty_csv builds the same
sub-binaries in C, 3.5-4x faster than NimbleCSV. Variant timings collect
the heap before each run, as Defk.Bench.report/1 does: without that, term
building looked 1.5-4x cheaper. Pre-registered marginal, measured poor.
…2.2x the best Elixir

In one long-lived process the verdict moved from 0.81x to 2.08x run to
run with the heap the earlier runs left. A fresh process per parse is a
request handler's case and repeats to 2%. Two more Elixir variants (E2:
:binary.split lines and fields, quoted lines walked with :binary.match;
E3: quoted pieces joined); E2 is the baseline, 5-7% faster than
NimbleCSV. The cleaned copy: 2.23x, good (pre-registered marginal).
rusty_csv is 2.6x faster than the copy.
…runs

mix defk.examples --bench --only tape_csv --snapshot --fresh from a clean
tree: quiet, 2.09x over E2, rusty_csv 49.5 ms. The header keeps its line
count, so the stamped C is the committed kernels'.
@DanielLuning
DanielLuning merged commit fb040eb into main Oct 20, 2026
2 checks passed
@DanielLuning
DanielLuning deleted the defk-9p6.30/tape-csv branch October 20, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant