You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Each scan of a registered Dataset rebuilds its batches in Python: 73% of the GPT demo's forward passΒ #272
Scanning a table that xarray-sql registered from a Dataset costs about 1.5β2.3 ms more than scanning the same rows from an in-memory DataFusion table. Each scan calls the partition's Python stream factory under the GIL (PyArrowStreamPartition::execute in src/lib.rs), which builds the record batches from the Dataset again. That doesn't matter for one scan of a large dataset. But a query that reads small tables many times pays it on every read: DataFusion inlines a CTE or a view at each reference (#267), so the plan scans the same tables thousands of times.
For the GPT in benchmarks/ml/02_nanogpt.py (#269), registering the same eleven parameter tables and the batch as in-memory tables makes:
registered by xarray-sql
in-memory (register_record_batches)
forward loss query (2,989 scans)
9.2 s
2.5 s
one training step through ddx, cached program
1.17 s
0.81 s
So 6.7 s of the 9.2 s forward pass, 73%, is the per-scan cost, and the loss's plan size (#267) multiplies it.
Reproduction
importtimeimportnumpyasnpimportxarrayasxrimportxarray_sqlasxqlctx=xql.Context()
ds=xr.Dataset({"w": (("tok", "e"), np.random.rand(65, 16))})
ctx.from_dataset("wte", ds)
batches=ctx.sql("SELECT * FROM wte").to_arrow_table().to_batches()
ctx.register_record_batches("wte_mem", [batches])
fornamein ["wte", "wte_mem"]:
ctx.sql(f"SELECT * FROM {name}").to_arrow_table()
start=time.perf_counter()
for_inrange(100):
ctx.sql(f"SELECT * FROM {name}").to_arrow_table()
print(name, (time.perf_counter() -start) *10, "ms per scan")
On an Intel i7-8700 with DataFusion 54.0.0, SELECT * FROM wte takes 1.69 ms through xarray-sql against 0.25 ms in memory. A join and GROUP BY of two such tables takes 5.6 ms against 1.8 ms.
Possible fixes
For a Dataset whose variables are already in memory (NumPy-backed, not Dask or a lazy backend), build each partition's batches once, at registration or on the first scan, and keep them. Scans then cost what a MemTable does. Memory is the trade-off: a cached Dataset is held twice, as arrays and as Arrow.
Or cache a partition's batches by projection for the life of the table, so repeated scans in one plan share them.
Either way, the cache must be invalidated when a table is registered again under the same name.
Root cause: most of the per-scan cost is datafusion-ffi, not xarray-sql. Python's conversion of a block to Arrow is 0.28 ms of the 1.7 ms. The rest is datafusion-ffi rebuilding the session on each scan and the task context on each partition's execution, every registered function included (#273, to re-file upstream).
So caching batches behind the FFI provider wouldn't help. The fix here registers a Dataset that is already in memory and small (under 64 MiB as long-form Arrow) as a native DataFusion in-memory table, built once at registration, which bypasses FFI. Lazy and large Datasets keep the lazy provider, with partition pruning and projection pushdown. On the GPT demo, this takes the forward loss from 9.2 s to 2.5 s and a cached training step from 1.17 s to 0.81 s.
Activity
alxmrs commentedon Oct 21, 2026
π Claude
Root cause: most of the per-scan cost is
datafusion-ffi, not xarray-sql. Python's conversion of a block to Arrow is 0.28 ms of the 1.7 ms. The rest isdatafusion-ffirebuilding the session on each scan and the task context on each partition's execution, every registered function included (#273, to re-file upstream).So caching batches behind the FFI provider wouldn't help. The fix here registers a Dataset that is already in memory and small (under 64 MiB as long-form Arrow) as a native DataFusion in-memory table, built once at registration, which bypasses FFI. Lazy and large Datasets keep the lazy provider, with partition pruning and projection pushdown. On the GPT demo, this takes the forward loss from 9.2 s to 2.5 s and a cached training step from 1.17 s to 0.81 s.
Register small in-memory Datasets as native in-memory tables
Register small in-memory Datasets as native in-memory tables