Chart · milliseconds

load the CSV, parse and type.

neo 2.89 ms in the language study, 2.77 ms in the library run. Closest: Swift at 1.14×. Farthest: Spark at 130×.

—

neo crosses once

neo

TAKE THIS CHART WITH YOU

Embed anywhere.

<iframe src="https://n0.is/embed/chart/load" title="load the CSV, parse and type" width="100%" height="520" loading="lazy" sandbox="allow-scripts" referrerpolicy="no-referrer" style="border:0;border-radius:12px"></iframe>

Every engine's multiple of neo

  1. neo
    1×
  2. Swift
    1.14× · 3.29 vs 2.89
  3. Rust
    1.22× · 3.53 vs 2.89
  4. Zig
    1.39× · 4.03 vs 2.89
  5. Go
    1.72× · 4.98 vs 2.89
  6. Polars
    5.49× · 15.2 vs 2.77
  7. DuckDB
    18.7× · 51.8 vs 2.77
  8. pandas
    82.6× · 229 vs 2.77
  9. Spark
    130× · 359 vs 2.77

Bars to scale, from zero to the slowest engine. The gray pair is the engine's measurement and neo's in the same run, in ms. The languages and the libraries were measured in separate runs, so neo has a number in each; every multiple is against neo in its own run.

What this measures

Read a 46 MB CSV of a million rows and six columns into a table: split the bytes into fields, parse the amount column into numbers, and dictionary-encode the region column. Each engine's typed read, best of two, with the first read of a fresh process kept separately.

What neo is held to

The same table the app loads, with the amount column typed and the region column encoded at load, so that no kernel after it touches text. Before a time is taken every engine prints its table as one number and its checks, and the line under the table says whether they agreed.

Each side, this kernel only

Swift1.14× neo · neo vs Swift

Written in-house; it stays with the engine. How it was run and checked is on its comparison page.

Rust1.22× neo · neo vs Rust

Written in-house; it stays with the engine. How it was run and checked is on its comparison page.

Zig1.39× neo · neo vs Zig

Written in-house; it stays with the engine. How it was run and checked is on its comparison page.

Go1.72× neo · neo vs Go

Written in-house; it stays with the engine. How it was run and checked is on its comparison page.

Polars5.49× neo · neo vs Polars

Polars, this kernel · neon-cpp/bench/lang/python/yardstick.py:218
res["load_first_ms"], res["load_ms"] = load_twice(lambda: pl.read_csv(args.csv))

DuckDB18.7× neo · neo vs DuckDB

DuckDB, this kernel · neon-cpp/bench/lang/python/yardstick.py:237
res["load_first_ms"], res["load_ms"] = load_twice(lambda: con.execute("CREATE OR REPLACE TABLE t AS SELECT * FROM read_csv(?, header=true)", [args.csv]))

pandas82.6× neo · neo vs pandas

pandas, this kernel · neon-cpp/bench/lang/python/yardstick.py:197
res["load_first_ms"], res["load_ms"] = load_twice(lambda: pd.read_csv(args.csv))
_, res["load_pyarrow_ms"] = load_twice(lambda: pd.read_csv(args.csv, engine="pyarrow"), "load_pyarrow_mj")

Spark130× neo · neo vs Spark

Spark, this kernel · neon-cpp/bench/lang/python/yardstick.py:255
def load():
    global df
    if df is not None:
        df.unpersist(blocking=True)
    df = spark.read.csv(args.csv, header=True, inferSchema=True).cache()
    df.count()  # force the read into memory; inferSchema has already read the file once more for types
res["load_first_ms"], res["load_ms"] = load_twice(load)

neo · confidential

neo's kernels, the ports written in-house to beat them, and the techniques behind both stay with the engine. Nothing about how the numbers were made is withheld: the method, the checks every engine had to pass, every number and every place neo is behind are on this page. The engine itself we keep confidential, because we don't want it getting into the wrong hands. The binaries run on request and reproduce every number here.

Measured on Apple M5 Max, 18 cores, 26.4.1, 2026-10-11 and 2026-10-15. Every number here is also on the comparison page of its engine, with how it was taken and the caveats.

All evalsEvery comparisonStart a conversation