The linq chain you write is a macro. It can compile to SQL and run against a real database engine — SQLite, DuckDB, a live PostgreSQL — or fuse at compile time into one pass over das's own containers: an array, decs entities, an XML DOM, a JSON tree, a hash table. Below, the same query families run both ways — first against the engines, then across the containers — interpreter and JIT, in nanoseconds per element.
require daslib/linq_boost
struct Car {
brand : string
price : float
}
[export]
def main {
var cars <- [Car(brand = "Ka", price = 900.0),
Car(brand = "Vaz", price = 1200.0)]
let stats <- _fold(each(cars)
._group_by(_.brand)
._select((Brand = _._0,
Avg = _._1 |> select($(c : Car) => c.price)
|> average()))
.to_array())
for (s in stats) {
print("{s.Brand}: avg {s.Avg}\n")
}
}
Every row is one query family; the chain compiles to SQL at compile time and the engine does the work — three engines side by side over the same schema: 100 000 Car rows, 100 dealers, 5 brands. The das array lane runs the same chains with no database at all — fused linq folds — and is the baseline every engine is paying its overhead against. PostgreSQL numbers include the same-box TCP round trip: the honest client-side view of a networked engine, not engine-core speed (a fully dashed PostgreSQL column means no server was reachable when the sweep ran). Cells are nanoseconds per element, lower is better; the fastest measured lane in each row is highlighted. Click a column to sort by it.
sqlite provider — the row store.array<Car>.| loading benchmark data… |
One front end, interchangeable back ends: the chain does not change when the engine does. The engine lanes live in an installable example package — the same providers any daslang program gets through daspkg.
The array lane above is this table's baseline. Here the same query families run over the rest of das's data sources — decs entities, an XML DOM, a parsed JSON tree, a hash table — with every chain fused at compile time into a single pass over its container; no database anywhere in this table. Same schema, same discipline: each lane's fixture is built once, one process per lane. Cells are nanoseconds per element; the fastest measured lane in each row is highlighted.
_fold over each(array<Car>) — the chain fuses into a single pass over the array._fold over from_decs_template — the same fusion as a per-archetype walk over ECS storage._fold over from_xml_node — one DOM walk, reading only the fields the chain uses._fold over from_json — the XML lane's mirror over a parsed JSON tree._fold over each_kv(table<int; Car>) — a fused slot walk; a key-equality where folds to an O(1) probe.| loading benchmark data… |
The speed is the macro system: the chain is rewritten at compile time — filters, projections and aggregates fused into one loop, unused fields pruned, key lookups turned into probes — and the JIT column is that fused loop compiled to native code.
the linq tutorial → what fuses, and when → benchmark source →
dastest --bench harness.Car schema everywhere — 100 000 rows, 100 dealers, 5 brands — materialized natively in each source: an array, a decs world, a parsed DOM, a JSON tree, a hash table, a :memory: SQLite database.first, any, a bounded take — finish below timer resolution. Those queries are effectively free; the cell is real, not missing data.last() on an unordered SQL result, zip over ECS archetypes). Every dash in the matrix has a stated reason in results.md; on the engine board a fully dashed PostgreSQL column means no server was reachable.benchmarks/sql/, the engine lanes in examples/benchmarks/sql/; each suite's results.md carries the sweep commands and the tool that regenerates both its tables and this page's data records.