Chart · milliseconds

load the CSV, parse and type.

neo 3.59 ms in the language study, 2.77 ms in the library run. Closest: Swift at 0.99×. Farthest: Spark at 130×. Swift ahead of neo here.

—

neo crosses once

neo

Every engine's multiple of neo

  1. neo
    1×
  2. Swift
    0.99× · 3.54 vs 3.59
  3. Rust
    1.28× · 4.60 vs 3.59
  4. Go
    1.44× · 5.17 vs 3.59
  5. Zig
    1.47× · 5.28 vs 3.59
  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.

Why neo is quick here

neo's parser finds the structural characters with SIMD, splits the file into blocks and parses them on every core above a megabyte, with results identical to the serial path. Typing and the dictionary happen once, at load, on the pool, so no kernel after it touches text. In the ledger's breakdown that is 2.8 ms of parse, 0.2 ms of typing and about nothing for the dictionary.

The code that ran, this kernel only

Swift0.99× neo · neo vs Swift

The port's load is the whole program: neon-cpp/bench/lang/swift/main.swift.

Rust1.28× neo · neo vs Rust

The port's load is the whole program: neon-cpp/bench/lang/rust/src/main.rs.

Go1.44× neo · neo vs Go

The port's load is the whole program: neon-cpp/bench/lang/go/main.go.

Zig1.47× neo · neo vs Zig

The port's load is the whole program: neon-cpp/bench/lang/zig/main.zig.

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

The kernel's body stays in the engine. Its declaration and the guarantees it is held to are below, with the harness line that calls it.

the harness, calling the engine · neon-cpp/bench/bench_engine_options.cpp
 194  l.t = csv::parse(raw);
 337  const double f5 = timeIt([&] { Bitmap b = compareDoubles(amt.data(), n, Cmp::Gt, 500, &pool); sink = b.words[0]; }, 7);
 372  const double s4 = timeIt([&] { sortIndexByDouble(amt.data(), n, perm, &pool); sink = perm[0]; }, 3);
 454  const double a5 = G <= 16 ? timeIt([&] { groupSumAnyOrder(amtP, idP, n, G, &pool, pairTotal); sink = static_cast<size_t>(pairTotal[0]); }, 9) : 0;
the parser's contract · neon-cpp/core/csv.h:55
Table parse(std::string_view raw);  // parallel above 1 MB, serial below; identical results

Measured on Apple M5 Max, 18 cores, 26.4.1, 2026-10-11 and 2026-10-12. The language study ran on a shared machine; its columns stand beside each other, but its times read high. Every number here is also on the comparison page of its engine, with the whole file it came from.

All evalsEvery comparisonStart a conversation