New in 0.6.1Skewed keys left to the CPU engine, Chao1 without replacement, pyarrow 26.0.0 →
Apache Arrow compute, on the Apple silicon GPU

The GPU does the scan. Your CPU does your app.

Arrow columns already sit in memory the GPU can read. ArrowMetal runs the reductions, filters, joins, sorts and group-bys there, and hands the result back as an Arrow array. Copy-free out, always; copy-free in when the producer's buffers are page aligned, one copy otherwise; and the CPU stays free for the rest of your program. The rows at or above 3x are led by group-bys and sorts, with reductions, window functions and string scans behind them, over columns that stay on the GPU between calls; under a million rows the dispatch is the operation.

Apache Arrow is the columnar memory format Polars, pyarrow, DuckDB and pandas already share. Unified memory means the Apple GPU reads the same bytes the CPU does, so there is no bus to cross.

Get it See the numbers Source on GitHub
0.6.1: pip install arrowmetal, arrowmetal = "0.6.1" and datafusion-arrowmetal = "0.6.1" on crates.io, the Swift package from the repositorylisted on Apache Arrow's Powered By page and Apache DataFusion's integrations listApache-2.0Apple siliconPython · Swift · C ABI
one unified memory · the same columns, read in place
ArrowMetal · the scan on the GPU
ArrowMetal

DuckDB 1.5.5 on this row, measured 2026-09-12 with the same data and protocol: 9.0 ms on its native table, so 1.8x. It is the closest CPU engine here; from 100,000 groups upward and on every sort it is 3x or more behind. The full comparison.

Engines

Inside DataFusion, Polars and DuckDB, with each engine's own answers.

ArrowMetal runs inside three query engines. Each one plans the query as it always does; ArrowMetal takes the parts of the plan measured faster on the Apple GPU, hands back what the engine would have returned, and leaves every other node to the engine. Every figure below comes from a run recorded in the repository, linked from its card.

DataFusion

A physical optimizer rule for DataFusion 55.1

Registered on a SessionContext, the rule replaces DataFusion's full sorts and the hash aggregates of measured shapes with MetalExec, which runs them on the GPU and hands DataFusion the RecordBatches it expects. The SQL does not change.

6.9x to 28.8xfull ORDER BY sorts against DataFusion alone, 250,000 to 50,000,000 rows; at the largest, 96 to 126 CPU-ms against DataFusion's 6,104 to 8,311
2.31x to 4.27xthe ten group-by series the default takes, warm; first run against first run after 500 ms of idle, 1.24x to 2.17x, and after 5 s, 1.11x to 1.68x
13,632query pairs, each run with and without the rule and the answers compared: 0 mismatches

The default takes full sorts from 250,000 rows, over DataFusion's own Parquet reader too (10.8x to 16.5x with a Float64 key, 3.6x to 5.5x over a file with a string column), and ten group-by series over integer keys of a MemTable: count, DISTINCT and min/max over an integer column, each from 50,000,000 rows, each at the numbers of groups where it was measured ahead, estimated from a sample when the query runs. Top-k, filters and every other aggregate stay DataFusion's; rule.report() gives each decision and its reason.

use std::sync::Arc;
use datafusion::{datasource::MemTable, prelude::*};
use datafusion_arrowmetal::{
    session_context, ArrowMetalConfig, ArrowMetalRule};

let rule = ArrowMetalRule::new(ArrowMetalConfig::default());
let ctx = session_context(SessionConfig::new(), rule.clone());
let t = MemTable::try_new(batch.schema(), vec![vec![batch]])?;
ctx.register_table("t", Arc::new(t))?;
ctx.sql("SELECT * FROM t ORDER BY amount DESC")
    .await?.show().await?;      // as written
println!("{}", rule.report()); // TAKEN / LEFT, why
Polars

A MetalEngine for lf.collect(engine=…)

Polars optimises the lazy plan as it always does. MetalEngine replaces the subtrees it translates with ArrowMetal plans that run on the GPU and return Polars DataFrames; Polars' in-memory engine runs everything else, on Polars 2.0.0 too, where a plain collect() runs the streaming engine.

1.52x to 11.54xagainst the faster Polars 1.44.1 engine, in-memory or streaming, on every one of the 75 subtrees the default took across 220 benchmarked case-size pairs
1.35x to 7.94xagainst the faster Polars 2.0.0 engine on every one of the 52 of 107 benchmarked cases at 50M rows the default took, none behind
15,376generated cases against Polars 1.44.1: 15,096 identical, 33 within the float-summation bound, 0 different

The default takes the shapes measured ahead, from one table per Polars major. On Polars 1.x: full sorts from 1,000,000 rows, inner, left and anti joins, unique, String sorts and unique from 5,000,000 rows, a sort over a Parquet scan, and group-bys judged by their estimated number of groups. On Polars 2.x: numeric sorts from 10,000,000 rows and unique from 2,943,034; String sorts and the group-by sums and means at 200 to 10,000 groups, but for two buckets, stay with Polars. Top-k, filters, whole-frame aggregates and semi joins stay with Polars; engine.last_report names each node and the reason.

import polars as pl, arrowmetal as am

engine = am.MetalEngine()  # the measured defaults
out = (orders.lazy()
         .join(customers.lazy(), on="customer_id")
         .sort("amount", descending=True)
         .collect(engine=engine))  # Polars' answer
print(engine.last_report)  # Metal or Polars, per node
DuckDB

An optimizer extension; the SQL is unchanged

the rewrite extension, built once

Loaded into a connection, the rewrite extension puts ARROWMETAL_AGGREGATE where DuckDB's HASH_GROUP_BY or UNGROUPED_AGGREGATE would have been. The scan, the pushed-down filters and everything above the aggregate stay DuckDB's, planned and run as before.

1.08x to 5.32xagainst DuckDB's own operators, on every query the default (auto) rewrote in the benchmark
33,376generated queries with the rewrite off and forced: 21,844 rewritten and identical, 11,532 left to DuckDB, 0 different

The default takes sum, avg, min, max and count over integer, DATE and TIMESTAMP columns, grouped by one column or none, over a table scan or read_parquet: a fused group-by of an estimated 10,000 groups or more from 10,000,000 rows, the other measured classes from 50,000,000. A single sum, few groups with at most two aggregates, and VARCHAR keys stay DuckDB's; arrowmetal_rewrites() logs each decision.

LOAD
  'duckdb-extension/build/arrowmetal_rewrite.duckdb_extension';

SELECT region, sum(amount), count(*), max(amount)
FROM sales GROUP BY region;          -- as written

SELECT * FROM arrowmetal_rewrites(); -- each decision
SET arrowmetal_rewrite = 'off';      -- plain DuckDB
What's new

New in 0.6.1.

Released on 2026-11-08: pip install arrowmetal, or arrowmetal = "0.6.1" and datafusion-arrowmetal = "0.6.1" on crates.io.

Metal

Sorts and group-bys on M1 GPUs

On an M1 Max, Apple's GPU compiler aborts on a 64-bit value joined with shifts from two threadgroup atomics, so 0.6.0's sorts of 64-bit keys and its privatised group-bys failed to create their pipelines there: 61 of 73 Sort tests. Four kernels now join with as_type, the same bits on every Apple GPU, checked on 65,536 word pairs. The fix is the first outside contribution, by d-polikhranidi.

Findings →
Polars

Polars 2.1.0

MetalEngine runs on Polars 2.1.0 (IR 16), and the polars extra installs polars>=1.44,<2.2. With the 2.0.0 crossover table the default takes 52 of 107 benchmarked cases at 50M rows, each 1.31x to 10.31x faster than the faster Polars 2.1.0 engine, none behind.

Findings →
Engine

A join that holds a filter

Polars 2.1 shows engines a join with a comparison over both sides inside it (join(...).filter(x < y) becomes one inner join of kind fused_predicate). Reading only the keys would return every pair of rows that shares a key; the engine runs the join, then the filter, and equals Polars in the five shapes tested. Other join kinds with a predicate stay with Polars.

How it works →
Conformance

The grid on 2.0.0 and 2.1.0

The engine conformance grid ran on Polars 2.x for the first time: 15,376 cases, 0 unclassified on 1.44.1, 2.0.0 and 2.1.0. The 36 divisions by a literal over frames under 100 rows where Polars 2.x's streaming collect() divides element-wise, and the engine gives the in-memory engine's bits, are a documented divergence.

Findings →
Also new in 0.6.0released 2026-11-06
Who it is for
  • You analyse data on a Mac with Polars, pyarrow, DuckDB or pandas, the scans and group-bys are the part you would hand to the GPU, and the columns can stay there between calls.
  • You build a tool or a service on Apple silicon and want the CPU back for the application while the GPU does the columns.
  • You maintain an engine and want an Apple-silicon GPU backend that speaks Arrow at every edge, without writing Metal.
Get it

Install, load, query.

Built and tested on an M4 Max; the wheel is checked in a clean virtual environment, with pyarrow as its only dependency. The Swift package resolves from the v0.6.1 tag. The ⧉ mark copies a block or a command.

Python

pyarrow in, one kernel, pyarrow out

import pyarrow as pa, arrowmetal as am

cols = {k: am.array(t[k]) for k in t.column_names}
region, amount = am.col("region"), am.col("amount")
q = am.filter((region == 2) & (amount > 100)).sum(amount)
am.query(cols, q)     # one Metal kernel, one round trip
cols["amount"].top_k(100).to_arrow()   # back to pyarrow
Full example →
DataFusion

the optimizer rule, from crates.io

Cargo.toml

The crate is on crates.io:

# Cargo.toml
[dependencies]
datafusion = { version = "=55.1.0", default-features = false, features = ["sql"] }
datafusion-arrowmetal = "0.6.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Full example →
Polars

the engine on a lazy plan

import polars as pl, arrowmetal as am

df = pl.read_parquet("orders.parquet")
engine = am.MetalEngine()      # Polars' own answers
top = (df.lazy().sort("amount", descending=True)
         .head(100).collect(engine=engine))
print(engine.last_report)     # what ran on Metal, and why
Full example →
DuckDB

unchanged SQL, aggregates on the GPU

the rewrite extension, built once
import arrowmetal as am

con = am.duckdb_connect()   # DuckDB + rewrite extension
con.sql("CREATE TABLE sales AS "
        "SELECT * FROM 'sales.parquet'")
con.sql("""SELECT region, sum(amount), count(*)
           FROM sales GROUP BY region""").show()
# as written; eligible aggregates run on the GPU
Full example →
pandas

an .am accessor on Series and DataFrame

import pandas as pd, arrowmetal as am
import arrowmetal.pandas_bridge       # adds .am

df = pd.read_parquet("trades.parquet",
                     dtype_backend="pyarrow")
df["price"].am.sum()
df["symbol"].am.contains("BRK")
df.am.groupby("symbol").sum("size")
Full example →
Swift

the library itself

Package.swift
// Package.swift
.package(url: "https://github.com/singhpratech/ArrowMetal",
         from: "0.6.1")

import ArrowMetal
let ctx    = try MetalContext()
let amount = try MetalArray<Int64>(values, context: ctx)
let mask   = try amount.compare(.gt, 100)
let total  = try amount.filter(where: mask).sum()
Full example →
Requirements

What it needs

macOS on Apple silicon. Python 3.10 or later. Builds with the Xcode Command Line Tools; no Metal toolchain needed because kernels compile at runtime. The wheel carries the library; pyarrow is its only dependency. C ABI for every other language: include/arrowmetal.h.

Why this exists

Unified memory changes the arithmetic of GPU analytics.

On a discrete GPU the data has to cross a bus before any kernel runs, so only long jobs pay off. On Apple silicon the Arrow buffer is already where the GPU reads, so a 3 millisecond scan is a 3 millisecond scan.

zero copy

Arrow buffers are Metal buffers

Every array is a shared-storage MTLBuffer with Arrow's exact layout. Import from Polars, pyarrow or any C Data Interface producer without copying when the producer's buffers are page aligned, with one copy otherwise, and the import result says which; export back copy-free. ArrowMetal implements ARROW_DEVICE_METAL from the Arrow C Device Interface.

gpu first

The full Arrow compute surface

Reductions, arithmetic, comparisons, strings, temporal, decimals, nested types, sorts, joins, window functions, grouped aggregates. Generated Metal kernels per element type, including IEEE-754 binary64 built in software because Apple GPUs have none in hardware: arithmetic and sqrt correctly rounded; exp/ln/log within 1-2 ulp; trig within 5 (asserted 6), where an ulp is one unit in the last place.

one kernel

Whole queries fuse

A filter, an expression and an aggregate compile to one Metal kernel at runtime, so each input byte is read once at memory bandwidth. On top of it: a lazy engine with an optimizer, six join kinds and an as-of join, a Parquet reader that decodes pages on the GPU, and bridges for Polars, DuckDB and pandas in 0.1.0.

The landscape

GPU dataframes existed for NVIDIA. We could not find Arrow compute on Metal.

Apache Arrow defines ARROW_DEVICE_METAL in its C Device Data Interface, which the project still marks experimental, and its nanoarrow library implements it as memory: page-aligned Metal buffers, allocation and copies, tested on Apple silicon runners, with no compute on them. ArrowMetal is the compute. Here is what we found, and what each one does on an Apple GPU, as of September 2026. If something is missing from this table, tell us and it will be corrected.

GPU dataframe and Arrow projects and where each one fits with ArrowMetal
ProjectWhat it isApple silicon GPUArrow compute on the GPUWhere it fits with ArrowMetal
ArrowMetalArrow compute engine on Metal: kernels, a fused expression compiler, a lazy query engine, a GPU Parquet reader, out-of-core streaming, and bridges for Polars, DuckDB and pandasyes312 Arrow functions audited: 284 GPU, 17 host, 7 partial—
cuDF (RAPIDS)GPU dataframe library, pandas-compatible APIno, NVIDIA CUDA onlyyes, on NVIDIAthe mental model; ArrowMetal is an Apple-silicon counterpart without CUDA
Polars GPU enginePolars lazy plans executed by cuDFno, NVIDIA Volta+ on Linux or WSL2yes, on NVIDIAPolars stays a CPU engine on Macs; ArrowMetal hands it Arrow arrays zero-copy
pyarrow, Polars, pandas, DuckDBCPU analytics enginesCPU onlynothe baselines in every table on this site; all accept Arrow from ArrowMetal without a copy
gpudb (DuckDB community extension)GPU aggregates and joins for DuckDB on CUDA and Metal, by the same authoryesno, SQL vectors, not Arrow buffersthe sister project; ArrowMetal is the Arrow-native engine, and its own DuckDB extension is the Arrow-side counterpart
nanoarrow device extensionArrow C Device Interface helpersMetal buffers: allocation, alignment and copies; experimentalnoArrow's own device plumbing; ArrowMetal implements the same interface with kernels behind it and can hand buffers to it
arrow-swiftArrow format library for Swiftno GPUnoformat only; ArrowMetal is independent of it
MLX, mlx-dataApple's array framework for ML and its data loaderyes, for tensorsno Arrow, no dataframe operatorsdifferent problem; ArrowMetal does not depend on MLX
ADBCArrow-native database connectivity API and driversn/ano, it moves data, it does not computea way to get Arrow batches into ArrowMetal from any database

Claims about other projects come from their public documentation at the time of writing; corrections welcome as an issue on the repository.

Upstream tracker

Upstream tracker: what the matrix and the bindings found in other projects, reported with a reproduction, and where each report stands.

Twenty findings across six projects: eighteen reported to the projects, as our own issues or as evidence added to an existing one, seven of them fixed upstream; one pyarrow crash withheld until it reproduces; one Metal compiler abort worked around in the kernels, with the reproducer in our own issue tracker. Each divergence the matrix carries has a test in the repository that fails on the day upstream fixes it, so the workaround can be removed. Nothing here is a complaint about another project: every entry is a divergence the matrix has to carry.

Apache Arrow (pyarrow)

8 findings · 7 reported
  • pc.utf8_normalize decomposes whatever form says (its NFC output is NFD) fixed upstream: apache/arrow#51225
  • pc.pairwise_diff on a sliced array ignores ArrowArray.offset fixed upstream: apache/arrow#50524
  • pc.fill_null_forward, pc.fill_null_backward, pc.replace_with_mask on a sliced boolean array ignore the offset fixed upstream: apache/arrow#51223
  • pc.winsorize on a sliced array nulls the wrong rows and places values where the input has nulls fixed upstream: apache/arrow#51224
  • pc.binary_slice with its own default stop raises ArrowInvalid: Negative buffer resize filed: apache/arrow#51222
  • pc.year_month_day and pc.iso_calendar crashed the process in the first matrix run withheld
  • A filtered Parquet read drops NaN rows when the filter is true for NaN (!=, negated comparisons, is_nan when min == max, is_null(nan_is_null=True), isin([nan])) and the file has row-group statistics… filed: apache/arrow#51491
  • DST rules stop being applied after the last tabulated transition (2037), so a summer instant in 2050 gets the winter offset, on a fat TZif file tracked on an existing report: apache/arrow#36110

Apple Metal

4 findings · 3 reported
  • the Feature Set Tables say the Apple9 family has "the full set of 64-bit atomic operations", but the shading-language headers admit device ulong only for atomic min and max… filed: Feedback Assistant FB24858110
  • makeComputePipelineState fails sporadically on the "Apple Paravirtual device" GitHub-hosted runners expose filed: Feedback Assistant FB24858160
  • a persistent GPU worker polling shared memory cannot be made to work: CPU/GPU coherence and the driver's submit-and-notify floor filed: Feedback Assistant FB24858235
  • the GPU compiler on an M1 Max (macOS 27.2 26B123, Xcode 27.2) aborts (XPC_ERROR_CONNECTION_INTERRUPTED… worked around: #2

Apache Arrow JS

1 finding · 1 reported
  • no C Data Interface export or import in the JavaScript library, so the Node binding builds the ArrowArray/ArrowSchema structs itself from a Vector's buffers… tracked on an existing report: apache/arrow-js#88

arrow R

2 findings · 2 reported
  • the C Data Interface entry points (Array$export_to_c, Array$import_from_c) exist but the only allocators for the structs they take (allocate_arrow_array(), allocate_arrow_schema()) are… tracked on an existing report: apache/arrow#39793
  • Array$create() on an R double or integer vector borrows R's memory, whose data begins 48 bytes into a page-aligned allocation, so the values buffer sits at offset 48 mod 64… closed by the project: apache/arrow#51227

Apache Arrow Go

4 findings · 4 reported
  • the compute package registers 85 scalar and vector functions and no aggregate at all (no sum, mean, min_max, count, no hash_*), and its package doc does not say so… filed: apache/arrow-go#1296
  • no allocator promises an alignment: NewGoAllocator buffers inherit Go's 8 KiB spans, so a buffer starts at offset 0 or 8192 within a 16 KiB page and page-aligned import is a coin flip whose odds… fixed upstream: apache/arrow-go#1297
  • compute.NewDatumWithoutOwning returns the same Datum type as an owning datum, so an over-release compiles and frees buffers under a live array with no error… fixed upstream: apache/arrow-go#1298
  • and_kleene and or_kleene return false where the answer is null when ExecCtx.ChunkSize is below the input length (same code on main 281776d)… fixed upstream: apache/arrow-go#1305

Swift

1 finding · 1 reported
  • in a generic throws function compiled at -O in its own module, the closure passed to withUnsafeBytes uses the error register x21 as scratch around an Objective-C message send and exits by… reported by others: swiftlang/swift#90477

The full tracker, with the evidence behind each row and where every report stands, is the Upstream tracker page in Docs and docs/UPSTREAM.md in the repository.

From the matrix CSV

Against each library's fastest idiom, on every row that has one.

One representative row per operation from the full matrix, wall-clock milliseconds on an Apple M4 Max with 16 CPU cores, the best of up to five calls after a warm-up (a slow row is repeated fewer times, never fewer than twice; the count is in the CSV). Every CPU library is measured twice on the same data — its plain eager idiom, and the most parallel idiom it has for the same answer: Polars through pl.LazyFrame on the in-memory or streaming engine, pyarrow through its Acero query engine as a plan over 16 record batches (pandas' threaded paths, numexpr and numba, were not installed for this run; numpy's ufuncs are single-threaded). Each library column below shows the faster of its two, tagged with which; the small number after each library's wall time is the cores that call used, and the Compare tab shows every idiom side by side. Speedup is the fastest CPU idiom of any library divided by ArrowMetal's time; rows are sorted by it. Regenerated from the CSV on every build; the full table is the Compare tab.

One representative row per operation against each library's fastest idiom
FamilyOperationRowsArrowMetal msPolars mspyarrow mspandas / numpy msSpeedup
reductionsquantile(float64, 0.5)50M4.61178 lazy 6.8c369 1.0c366 1.0c38.7x
sortpartition_nth_indices (n/2)50M12.16—298 1.0c349 numpy 1.0c24.5x
compare+selecttake (n/2 random indices)50M5.90172 1.0c143 1.0c171 1.0c24.2x
sortlexsort (2 int32 keys)50M37.59903 lazy 11.0c7,774 1.0c29,655 numpy 1.0c24.0x
sortargsort utf810M13.73228 lazy 13.8c1,601 1.0c1,601 1.0c16.6x
sortrank (min)50M68.03700 5.6c5,553 1.0c21,473 1.0c10.3x
chainscompare + filter + take50M4.5445.72 1.0c124 1.0c202 numpy 1.0c10.1x
sorttop_k (k=100, int64)50M2.6765.08 lazy 1.0c24.00 1.0c328 numpy 1.0c9.0x
sortargsort int6450M39.15302 11.8c5,197 1.0c4,545 numpy 1.0c7.7x
windowcumulative_sum (float64)50M13.04132 1.0c99.50 1.0c120 1.0c7.6x
group-bysum by int32 key (10000000 groups)50M46.12322 lazy 14.3c1,303 acero 2.8c1,294 1.0c7.0x
group-bymean by int32 key (100000 groups)50M7.3999.19 lazy 14.7c47.02 acero 11.5c430 1.0c6.4x
group-bysum by utf8 key (100000 distinct)50M23.80178 12.9c145 12.3c783 1.0c6.1x
temporalassume_timezone (America/New_York)50M15.85205 lazy 15.4c92.53 acero 14.4c3,119 1.0c5.8x
reductionsmean(int64, 10% nulls)50M1.1513.25 1.0c6.11 acero 13.3c64.18 1.0c5.3x
stringsmatch_like("cust_1%") [_ wildcard]10M1.698.56 lazy 14.6c18.31 acero 12.1c203 1.0c5.1x
group-bymin by int32 key (10000000 groups)50M72.04329 14.3c1,314 acero 2.8c1,423 1.0c4.6x
sortsort float6450M32.12133 9.7c6,044 1.0c1,759 numpy 1.0c4.1x
group-bysum by two int32 keys (~1024 groups)50M5.91164 13.5c23.88 13.5c448 1.0c4.0x
stringschar_length10M1.104.22 lazy 14.8c5.45 acero 11.6c52.47 1.0c3.8x
group-bysum by int32 key (1000 groups)50M4.8981.93 lazy 14.5c18.45 acero 13.6c248 1.0c3.8x
group-bycount_distinct by key (1000 groups)50M41.78157 lazy 14.0c1,859 acero 2.3c4,773 1.0c3.8x
reductionsmode(int64)50M33.61132 lazy 14.6c104 1.0c342 1.0c3.1x
joinleft outer hash join (50000000 x 1000000 on int64)50M19.08285 5.0c57.95 14.8c1,690 1.0c3.0x
joininner hash join (50000000 x 1000000 on int64)50M21.80106 10.0c63.87 15.0c543 1.0c2.9x
stringsupper10M5.3225.14 lazy 14.2c15.29 acero 12.9c169 1.0c2.9x
element-wiseexp (float32)50M1.935.29 lazy 13.4c6.42 acero 12.6c61.94 numpy 1.0c2.7x
stringssplit_pattern("_")10M8.4725.36 lazy 12.9c20.74 acero 13.0c223 1.0c2.4x
reductionstdigest(float64, q=0.5)50M33.18—79.96 acero 15.3c—2.4x
temporalstrftime (%Y-%m-%d)50M151361 lazy 15.5c839 acero 15.4c4,629 1.0c2.4x
reductionssum(int64, 10% nulls)50M1.112.30 lazy 11.1c5.09 acero 13.0c51.54 1.0c2.1x
decimaldecimal add (128-bit)50M5.4221.56 lazy 14.1c11.04 acero 12.8c74.38 1.0c2.0x
element-wisemultiply scalar (int64)50M2.104.13 lazy 12.6c4.26 acero 11.5c54.87 1.0c2.0x
element-wiseadd_checked (int64 + int64)50M4.50—8.63 acero 12.1c—1.9x
nestedlist_value_length10M0.360.73 lazy 10.2c0.66 acero 9.6c0.79 1.0c1.8x
element-wisecoalesce (2 int64 columns)50M3.325.89 lazy 14.0c8.48 acero 12.5c147 1.0c1.8x
reductionsmin(int64, 10% nulls)50M1.182.01 lazy 10.1c5.05 acero 13.3c47.68 1.0c1.7x
temporaladd_duration (+1h)50M2.093.26 lazy 12.7c4.37 acero 12.0c183 1.0c1.6x
element-wiseround (float64)50M3.184.72 lazy 9.6c11.49 acero 12.3c110 1.0c1.5x
element-wiseif_else (bool ? int64 : int64)50M3.755.31 lazy 13.6c12.67 acero 12.5c14.61 numpy 1.0c1.4x
element-wiseadd array (int64 + int64)50M3.124.31 lazy 12.8c5.64 acero 12.0c148 1.0c1.4x
compare+selectfilter int64 (30% kept)50M2.232.85 lazy 12.6c22.61 acero 13.2c157 1.0c1.3x
group-byvariance by key (1000 groups)50M30.3683.36 14.4c29.17 12.7c239 1.0c0.96x
compare+selectdrop_null (int64, 10% nulls)50M4.203.81 lazy 13.4c11.14 acero 12.7c167 1.0c0.91x
element-wisesqrt (float64)50M3.923.42 lazy 12.7c3.74 acero 11.6c12.39 numpy 1.0c0.87x
sortunique (int32, 1000 distinct)50M15.72102 4.6c13.04 acero 13.6c63.76 1.0c0.83x
chainsfilter two columns + sum50M3.532.92 lazy 13.9c17.73 acero 13.5c112 numpy 1.0c0.83x
compare+selectcompare scalar (int64 > 0)50M2.411.91 lazy 11.8c3.53 acero 11.5c6.16 1.0c0.79x
temporalyear50M26.8232.65 lazy 15.2c17.91 acero 13.7c1,096 1.0c0.67x
temporalfloor_temporal (day)50M8.743.35 lazy 11.9c9.71 acero 12.5c44.82 1.0c0.38x
stringsparse (utf8 -> int64)10M19.593.15 lazy 14.6c5.46 acero 11.2c439 1.0c0.16x
element-wisesin (float64)50M15023.28 lazy 15.1c25.57 acero 13.7c301 numpy 1.0c0.16x
temporaldays_between50M46.315.75 lazy 13.8c6.66 acero 12.7c451 1.0c0.12x
element-wiseln (float64)50M81.318.46 lazy 14.2c9.85 acero 13.3c86.95 numpy 1.0c0.10x
stringsmatch_substring_regex (real regex)10M16714.57 lazy 14.3c29.32 acero 13.7c331 1.0c0.09x
windowshift (lag 1, int64)50M3.580.03 lazy 2.2c—0.15 1.0c0.01x

A dash means the library has no equivalent operation or was not measured for that row; where pandas has no idiom, the numpy one is shown and marked, and a small lazy or acero tag means that library's parallel idiom was the faster of its two. Polars' lazy engine used a median of 11.5 of the 16 cores across this run and pyarrow's Acero 11.1, against 1.0 for the eager idioms on most families; the per-row figure is in every cell. Every row is on the Compare tab with the processor time each call consumed. Bold speedups are at or above the project's 3x bar.

Open opportunities. Pick one.

Every comparison on this site is against the fastest idiom any CPU library has for the row — Polars' lazy engine (Polars 1.44.1) and pyarrow's Acero included, which used a median of 11.5 and 11.1 of the 16 cores across this run. None of them has a GPU path on a Mac. DuckDB is not in the matrix; measured separately on its sort, group-by, filter and sum rows on 2026-09-12, it is the fastest CPU engine on low-cardinality group-by and multi-key sort, and the group-by card at the top carries its number. 145 comparison rows clear 3x against that baseline, 102 are ahead but under it, and 77 are behind, all of them in the tables with the idiom and the cores that set the bar. Of the 77, 28 are single passes at unified-memory bandwidth and 16 are the fixed cost of one GPU dispatch below a million rows; the other 33 are in the items below. Each item names its measured cause, and each is open to a contributor:

  • Faster correct doubles. ln, sin and days_between are 0.10x to 0.16x at fifty million rows of a vectorised libm on 14 to 15 cores. Apple GPUs have no binary64 hardware, so these run emulated, arithmetic and sqrt correctly rounded; exp/ln/log within 1-2 ulp; trig within 5 (asserted 6), where the CPU has one instruction.
  • A GPU regular-expression engine. LIKE and literal patterns are GPU kernels at 1.6x to 7.0x at ten million rows; a real regex runs on the host through ICU, the Unicode regex library, at 0.09x to 0.14x.
  • Single passes at ten million rows. Element-wise arithmetic, compare and filter sit at 0.57x to 0.98x there: both sides move the same bytes through the same memory at close to the same rate, and 9 to 13 cores get there too.
  • Views. The matrix's shift writes a new column, 3.58 ms at 50M rows, which is this machine's bandwidth; Polars answers with a two-chunk view in 0.03 ms and moves nothing. shift(view=True) gives the same answer in 0.04 ms at 10M rows.
  • Temporal field extraction at fifty million rows. year, month and day are 0.66x to 0.67x of pyarrow's Acero: a calendar conversion is a few dozen integer operations an element on both sides.
  • Grouped moments at a few groups. Variance and stddev at a thousand groups are 0.96x to 1.24x, because Metal has no 64-bit atomic add and the moments accumulate in software binary64.
  • unique and value_counts. 0.55x to 0.84x on a thousand-distinct column against a threaded hash aggregation.
  • Calls under a million rows. A GPU command buffer costs 110 to 240 microseconds for a sum or a filter of a thousand to a million rows, and 400 to 1,100 for a group-by sum over 1000 keys; 60 to 70 microseconds of that is the empty round trip, and a CPU core finishes first. Batching and the fused compiler amortise it across a plan.
  • String conversions with variable-length output. parse (utf8 → int64) and to_strings are 0.16x to 0.57x: the output length of each row depends on its value, so the kernel measures, prefix-sums and writes in a second pass, against one streaming pass on the CPU.
  • Whole-query chains against a streaming engine. filter two columns + sum and group-by after filter are 0.43x to 0.83x of Polars' lazy engine, which fuses the predicate and the aggregate and runs it on 11 to 14 cores. A plan handed down from Polars or DuckDB is longer than the harness's one question, and every operator added to it amortises against the same dispatch.

Closed in the 2026-09-07 run, and now above the bar on the same baseline: the float64 sort's last gather (sort float64 3.8x to 4.1x) and the small-group aggregates (sum, mean and min by int32 key at a thousand groups, 3.3x to 3.8x).

The Join forces tab shows what already runs beside the engines we measure and what each path costs today.

Coverage by exact Arrow name

312 compute functions, audited one by one.

Every function name in the Arrow 25 compute list plus the grouped aggregates, each executed through call_function against pyarrow and recorded as GPU, host, partial or missing. The counts come from the registry the tests run; the status is what runs, not what was intended.

284
run as Metal kernels
17
run on the host, by design: ICU regex, t-digest merge, a handful of Unicode tables
7
partial: a missing option, a missing input type, or a stated difference in the answer, each one named
4
missing: names new in Arrow 26.0.0, recorded and not yet implemented; the other 308 names are callable
Differential41,688 cases against pyarrow.compute across 46 column types, every public method. 1,643 of them differ, across 22 documented divergences; none unclassified. Every difference is documented as an open finding, a deliberate divergence, or a pyarrow bug, each with a reproduction, and the fixed ones stay as regression tests.
Tests and review1,047 Swift test functions in release plus 7,289 collected Python test cases, on a physical Apple silicon Mac, then a review pass whose every finding carries a regression test. Every GPU kernel has a CPU reference; the hostile-input suites pin threadgroup boundaries, nulls and slices.
InteropC Data, C Device and C Stream import and export (the C Device Stream is not implemented); Arrow IPC read and write, including dictionary batches.
TypesAll primitives, boolean, utf8 and binary, temporal, decimal32 to 256, list, struct, map, union, dictionary, run-end encoded, float16, intervals, extension types.
How it works

Kernels at the bottom, an engine on top, one package.

in 0.1.0
KernelsMetal Shading Language generated per element type at runtime and cached. Reductions without atomics, radix sorts, privatised hash group-by, stream compaction.
in 0.1.0
Fused compilerAn expression tree with filters and aggregates lowers to a single kernel: nulls compiled to bit logic, shared subexpressions computed once, 4-wide loads.
in 0.1.0
Query engineLazy plans with predicate pushdown, projection pruning and fusion planning. Six join kinds and an as-of join over multi-column and string keys. explain().
in 0.1.0
Parquet on the GPUPages decoded by Metal: PLAIN, RLE, dictionary, delta, byte-stream-split. Snappy and LZ4 decompressed on the GPU.
in 0.1.0
StreamingData flows from disk through the GPU with reads and compute overlapped, and the group-by and top-k state resident on the GPU, measured to 30.21 GB on a 64 GB machine; RAM tracks the answer, not the scan. Hands batches to pyarrow or Polars through the C Stream interface.
Languages

Python and Swift natively; Rust, Go, TypeScript and R over one C header.

One library, one C header. The Python and Swift packages call the same kernels; every other language reaches them through include/arrowmetal.h with no Swift runtime in sight, which is how the Rust plugin and the C++ extension were built. Each binding is tested against its own ecosystem's kernels on the same data and carries measured timings in its doc, the rows where the ecosystem's own kernel is ahead included. Anything marked not started is an invitation.

Language bindings, how each reaches the kernels, and its status in 0.6.1
LanguageHowWhat it getsStatus
Pythonpip install arrowmetal (macOS 14 or later, Apple silicon), or from source; ctypes over the C ABI, no compiled extensionthe full API: arrays, fused queries, the lazy engine, Parquet, streaming, the Polars, DuckDB and pandas bridgesin 0.6.1: 7,289 collected test cases
SwiftSwift package, import ArrowMetalthe native API the Python package is built on: MetalArray, MetalRecordBatch, LazyFrame, ParquetFile, StreamQueryin 0.6.1: 1,047 test functions
Cinclude/arrowmetal.h, one dylibimport and export through the Arrow C Data and C Device interfaces, every kernel by name, a JSON plan runner for whole queriescompiled as C in the test suite
Rustthe arrowmetal crate on crates.io over the C ABI (rust/), arrow-rs arrays in and out through arrow::ffi; the Polars expression plugin uses the same -sys layerimport and export, reductions, compare, filter, take, slice, sort, group-by, the JSON plan runner, batching; the rest of the ABI is listed as not wrapped in the docin 0.6.1: 66 tests, 64 against arrow-rs plus 2 over the raw ABI and 5 doc-tests, three measured rows against arrow-rs in the Rust doc
Ran R package (r/arrowmetal) with a C shim over the ABI, columns crossing through the arrow R package's C Data Interfaceimport and export, reductions, compare, filter, take, slice, sort, group-by with the standard aggregates, the plan runner; int64 through bit64in 0.6.1: 88 test blocks in the sources, 920 expectations, against arrow and base R, timings in the R doc
TypeScript / JavaScripta Node addon over the C ABI with N-API (node/), exchanging columns with Apache Arrow JS through C Data Interface structs the addon builds itselfimport and export for the primitive, boolean and string types, reductions, compare, arithmetic, cast, filter, take, slice, sorts, group-by, the plan runner; the browser is out of scopein 0.6.1: 84 tests against Arrow JS and plain JS, timings in the TypeScript doc
C++the C ABI, as the DuckDB extension doesplans run from SQL, results back as Arrowthrough the extension today
Goa cgo module (go/arrowmetal) that loads the dylib and forwards the entry points it uses, typed from the header; Arrow Go arrays through its cdata package, buffers pinned for the life of the handleimport and export, reductions, compare, filter, take, sort, argsort, lexsort, group-by, the plan runnerin 0.6.1: 69 tests, 66 test functions and 3 runnable Examples, 584 passing results with subtests, green under the runtime's cgo checker, timings in the Go doc
Java, C#, Juliathe C ABI, through each language's FFIthe same header; nothing language-specific is requiredno binding in this repository

Each binding's doc carries its own measured timing table with the method stated, the rows where the ecosystem's own kernel is ahead included.

License

  • Apache License 2.0
  • Swift, Metal Shading Language and Python; no copyleft linked
  • © 2026 the ArrowMetal authors

Built for Apple silicon. Every number on this page comes from a run in this repository; the one estimate is marked as one.

Apache Arrow and the Arrow logo are trademarks of The Apache Software Foundation. Apple and Metal are trademarks of Apple Inc. ArrowMetal is not affiliated with or endorsed by the ASF or by Apple.

About the mark. Five arrowheads in a wake, cut like machined metal. Read from back to front they follow the order in which the pancha mahabhuta, the five elements of Sanatana Dharma, are said to manifest: akasha, space, is an outline only; vayu, air, the first dark solid; agni, fire, warmer; jala, water, the first to show its facets; prithvi, earth, the polished head in front, and metal is what the earth gives up. The point at the origin of the wake is the bindu. The arrow is the direction data flows; the facets are the light on metal; the five heads leaving together are columns finishing in one dispatch. It is a reading the mark supports, offered plainly, not a claim that it was designed from scripture.

Generated from Benchmarks/results/full_matrix_2026-09-07-parallel.csv

Every measured operation, every library, on the M4 Max.

173 operations at one to three sizes: 339 comparison rows, 15 of them with no CPU equivalent, built from 1,860 library measurements (the CSV holds 1,883 timed records: the nested family at 10M rows was measured twice and the later pass is shown). Apple M4 Max, 16 CPU cores, 64 GB unified memory. The best of up to five calls after a warm-up; a slow row is repeated fewer times, never fewer than twice, and the count is in the CSV. Each CPU library is shown in its plain eager idiom and, where the matrix has measured it, in the most parallel idiom it has for the same answer (Polars lazy; pyarrow through its Acero query engine over 16 record batches; pandas' threaded paths, numexpr and numba, were not installed for this run, and numpy stands in where pandas has no equivalent). Polars and pyarrow run 16-thread pools, unpinned. The small number after each library's wall time is the cores that call used, CPU-ms divided by wall ms. "Fastest CPU" is the best of every idiom for that row, named. Speedup is that wall time divided by ArrowMetal's; rows are sorted by it, strongest first.

145
at or above 3x the fastest CPU library (4 of them host short-circuits, any and all, noted on their rows)
102
faster, but under 3x
77
to improve: behind the fastest CPU idiom; the home page groups them by cause
15
not measurable yet (not reachable from Python, or no CPU equivalent)
Rows per family by verdict
Family≥3x1–3xto improven/arows
chains204612
compare+select3411018
decimal253010
element-wise52817050
group-by65181084
join42006
latency009918
nested22004
reductions16173036
sort2124027
strings81713038
temporal7710024
window1002012

How to read a row: bold in a library column marks the fastest CPU idiom for that row; hover or tap a cell for its CPU-ms and the exact idiom. The small line under each ArrowMetal time is its own processor time; the small line under an operation name is the CSV's note for that row. A dash in a lazy or Acero column means the matrix has not measured that idiom for the row yet, or the library has no parallel form of it. Rows marked to improve are behind the fastest CPU idiom; the home page lists the open opportunities behind them.

Every measured operation against every library
OperationRowsSpeedupVerdictArrowMetal ms Polars msPolars lazy mspyarrow mspyarrow Acero mspandas / numpy ms
quantile(float64, 0.5)50M38.7x≥3x4.6111.626 CPU-ms179.3 6.7c178.5 6.8c368.9 1.0c—366.5 1.0c
count by int32 key (10000000 groups)50M25.7x≥3x16.72.015 CPU-ms429.1 9.5c427.2 9.3c1,067 3.0c1,065 3.0c772.7 1.0c
partition_nth_indices (n/2)50M24.5x≥3x12.23.838 CPU-ms——297.8 1.0c—349.4 numpy 1.0c
take (n/2 random indices)50M24.2x≥3x5.9040.786 CPU-ms171.8 1.0c172.0 1.0c142.6 1.0c—171.4 1.0c
lexsort (2 int32 keys)50M24.0x≥3x37.62.416 CPU-ms920.1 10.9c902.9 11.0c7,774 1.0c—29,655 numpy 1.0c
dictionary_encode (utf8)10M20.1x≥3x5.6494.025 CPU-ms113.8 1.0c113.4 1.0c131.3 1.0c—137.3 1.0c
take (n/2 random indices)10M18.0x≥3x1.2780.638 CPU-ms28.9 1.0c28.6 1.0c23.0 1.0c—28.3 1.0c
count by int32 key (10000000 groups)10M17.3x≥3x5.0721.664 CPU-ms87.8 8.8c90.6 8.8c266.8 2.1c270.7 2.2c229.2 1.0c
argsort utf810M16.6x≥3x13.73.327 CPU-ms232.7 13.7c227.5 13.8c1,601 1.0c—1,601 1.0c
lexsort (2 int32 keys)10M15.3x≥3x7.2912.299 CPU-ms111.4 10.9c114.6 10.6c1,123 1.0c—3,371 numpy 1.0c
sum by two int32 keys (~10004569 groups)50M12.1x≥3x47.93.208 CPU-ms580.1 13.7c619.4 12.6c1,363 2.8c1,363 2.8c1,610 1.0c
list by key (1000 groups)50M10.9x≥3x28.43.945 CPU-ms319.0 3.2c308.4 3.3c400.6 1.6c394.1 1.5c8,179 1.0c
argsort float6450M10.5x≥3x39.90.825 CPU-ms420.1 13.2c422.7 13.1c5,885 1.0c—5,472 numpy 1.0c
dense_rank50M10.3x≥3x67.83.070 CPU-ms699.2 5.6c716.1 5.5c5,552 1.0c—21,375 1.0c
rank (min)50M10.3x≥3x68.03.078 CPU-ms700.1 5.6c708.8 5.6c5,553 1.0c—21,473 1.0c
compare + filter + take50M10.1x≥3x4.5451.132 CPU-ms45.7 1.0c46.5 1.0c124.4 1.0c—202.0 numpy 1.0c
rolling_sum (window 64)50M10.0x≥3x23.32.088 CPU-ms430.9 1.0c427.7 1.0c——234.1 1.0c
list by key (1000 groups)10M9.8x≥3x5.9142.878 CPU-ms58.6 3.5c58.1 3.5c73.5 1.5c74.5 1.6c1,621 1.0c
list by key (100000 groups)50M9.7x≥3x35.44.417 CPU-ms343.9 3.7c344.7 3.6c487.6 1.7c489.7 1.8c9,555 1.0c
argsort float6410M9.7x≥3x8.1070.851 CPU-ms78.6 12.8c80.3 12.6c984.1 1.0c—841.9 numpy 1.0c
quantile(float64, 0.5)10M9.7x≥3x3.6261.773 CPU-ms35.0 6.6c35.0 6.6c61.9 1.0c—62.3 1.0c
strptime (%Y-%m-%d %H:%M:%S)50M9.2x≥3x15.52.054 CPU-ms2,167 1.0c157.0 15.6c1,728 1.0c142.6 14.8c4,887 1.0c
top_k (k=100, int64)50M9.0x≥3x2.6741.380 CPU-ms65.5 1.0c65.1 1.0c24.0 1.0c—328.4 numpy 1.0c
strptime (%Y-%m-%d %H:%M:%S)10M8.9x≥3x3.3700.775 CPU-ms418.4 1.0c32.5 14.8c344.8 1.0c30.0 13.9c967.5 1.0c
rolling_sum (window 64)10M8.9x≥3x5.3481.734 CPU-ms86.7 1.0c85.7 1.0c——47.3 1.0c
partition_nth_indices (n/2)10M8.6x≥3x5.8233.631 CPU-ms——49.9 1.0c—54.6 numpy 1.0c
dense_rank10M8.5x≥3x12.01.801 CPU-ms110.7 6.3c102.7 6.8c883.7 1.0c—2,223 1.0c
rolling_mean (window 64)50M8.4x≥3x25.32.084 CPU-ms416.9 1.0c415.0 1.0c——213.3 1.0c
sum by int32 key (100000 groups)10M8.4x≥3x1.9511.082 CPU-ms22.7 12.1c24.8 11.6c16.4 6.0c21.6 6.4c67.5 1.0c
list by key (100000 groups)10M8.4x≥3x8.8273.447 CPU-ms74.5 3.4c74.1 3.4c88.8 1.7c92.4 2.0c2,533 1.0c
sum by two int32 keys (~10004569 groups)10M8.4x≥3x12.43.705 CPU-ms103.9 12.5c104.4 13.0c337.3 2.0c352.6 2.1c442.8 1.0c
rank (min)10M8.2x≥3x12.41.896 CPU-ms101.9 6.8c111.6 6.3c889.8 1.0c—2,260 1.0c
cumulative_prod (float64)50M8.1x≥3x12.40.409 CPU-ms134.4 1.0c135.7 1.0c100.0 1.0c—123.4 1.0c
first by key (1000 groups)50M8.0x≥3x6.8233.401 CPU-ms54.2 14.3c55.8 14.0c210.8 1.0c—196.6 1.0c
count by int32 key (100000 groups)50M7.8x≥3x4.9241.258 CPU-ms120.7 8.8c121.9 8.8c39.7 11.0c38.3 11.0c170.9 1.0c
argsort int6450M7.7x≥3x39.10.855 CPU-ms302.0 11.8c304.5 11.7c5,197 1.0c—4,545 numpy 1.0c
sum by two int32 keys (~100489 groups)10M7.7x≥3x2.4891.888 CPU-ms38.0 11.8c38.2 11.9c19.1 6.1c25.6 6.3c107.7 1.0c
cumulative_sum (float64)50M7.6x≥3x13.00.405 CPU-ms132.1 1.0c135.5 1.0c99.5 1.0c—120.2 1.0c
compare + filter + take10M7.6x≥3x0.9790.994 CPU-ms7.550 1.0c7.392 1.0c23.9 1.0c—40.3 numpy 1.0c
cumulative_prod (float64)10M7.5x≥3x2.6970.401 CPU-ms26.4 1.0c26.4 1.0c20.2 1.0c—25.2 1.0c
sum by two int32 keys (~100489 groups)50M7.4x≥3x8.2702.212 CPU-ms213.8 12.2c213.0 12.7c66.5 10.6c61.2 10.7c573.3 1.0c
argsort int6410M7.4x≥3x7.8110.826 CPU-ms57.6 11.4c57.8 11.3c841.2 1.0c—699.0 numpy 1.0c
is_in (int64, 100-value set)re-measured in place after the full run flagged it (same build, quieter machine)50M7.4x≥3x2.1681.507 CPU-ms269.7 1.0c16.0 15.0c383.7 1.0c33.2 14.6c406.6 1.0c
sum by int32 key (10000000 groups)10M7.4x≥3x9.4571.643 CPU-ms72.0 12.8c69.5 13.6c312.4 2.0c319.2 2.1c321.0 1.0c
rolling_mean (window 64)10M7.3x≥3x5.8721.761 CPU-ms85.5 1.0c85.6 1.0c——43.0 1.0c
count_distinct by key (10000000 groups)10M7.2x≥3x11.42.727 CPU-ms82.8 13.8c83.9 13.9c645.6 2.0c668.2 2.1c1,033 1.0c
cumulative_sum (float64)10M7.0x≥3x2.8330.427 CPU-ms26.9 1.0c26.0 1.0c19.9 1.0c—24.5 1.0c
contains("north")10M7.0x≥3x1.6400.394 CPU-ms141.8 1.0c11.5 15.0c118.8 1.0c13.8 11.1c119.3 1.0c
top_k (k=10000, int64)50M7.0x≥3x4.9601.887 CPU-ms66.2 1.0c65.5 1.0c34.7 1.0c—327.6 numpy 1.0c
sum by int32 key (10000000 groups)50M7.0x≥3x46.12.157 CPU-ms327.0 14.1c321.7 14.3c1,309 2.9c1,303 2.8c1,294 1.0c
mean by int32 key (10000000 groups)10M7.0x≥3x11.61.727 CPU-ms80.9 13.6c82.2 13.9c304.7 2.1c315.2 2.2c326.3 1.0c
max by key, float64 (100000 groups)10M7.0x≥3x2.5571.390 CPU-ms22.7 12.1c23.1 11.9c17.8 5.8c24.5 5.8c64.9 1.0c
pairwise_diff (float64)50M6.9x≥3x3.5730.889 CPU-ms25.5 1.0c25.6 1.0c102.5 1.0c—24.7 1.0c
min by key, float64 (100000 groups)10M6.8x≥3x2.5781.381 CPU-ms22.6 12.3c22.3 12.4c17.6 5.8c24.3 6.0c64.6 1.0c
mean by int32 key (10000000 groups)50M6.8x≥3x49.62.233 CPU-ms343.4 14.1c336.7 14.0c1,294 2.8c1,285 2.8c1,350 1.0c
count_distinct by key (10000000 groups)50M6.7x≥3x60.73.087 CPU-ms407.5 14.8c410.6 14.7c3,210 2.5c3,195 2.5c5,238 1.0c
max by key, float64 (100000 groups)50M6.7x≥3x9.6741.661 CPU-ms120.8 14.7c95.6 14.5c73.5 10.6c64.9 10.2c310.3 1.0c
min by key, float64 (100000 groups)50M6.6x≥3x9.7001.680 CPU-ms123.0 14.9c95.4 14.6c84.8 10.6c64.3 10.5c311.0 1.0c
count by int32 key (100000 groups)10M6.6x≥3x1.9581.132 CPU-ms28.2 7.2c27.4 7.6c13.0 6.3c17.1 6.4c32.2 1.0c
mean by int32 key (100000 groups)10M6.6x≥3x2.4781.167 CPU-ms23.5 12.0c23.3 12.2c16.2 6.0c20.8 6.6c85.6 1.0c
min by int32 key (100000 groups)10M6.5x≥3x2.7871.433 CPU-ms23.6 11.9c23.3 12.0c18.1 5.8c24.5 5.9c68.6 1.0c
max by int32 key (100000 groups)10M6.5x≥3x2.7781.432 CPU-ms23.5 11.6c24.5 11.5c17.9 5.8c25.0 5.8c68.7 1.0c
first by key (10000000 groups)10M6.4x≥3x14.62.630 CPU-ms94.0 8.3c98.2 8.2c307.9 1.0c—300.1 1.0c
assume_timezone (America/New_York)10M6.4x≥3x3.3630.708 CPU-ms540.3 1.0c41.0 15.4c215.3 1.0c21.7 12.1c669.1 1.0c
last(int64)50M6.4x≥3x0.0020.003 CPU-ms24.5 1.0c0.010 1.0c0.150 1.0c—35.2 1.0c
mean by int32 key (100000 groups)50M6.4x≥3x7.3891.207 CPU-ms101.2 14.8c99.2 14.7c47.3 11.4c47.0 11.5c429.6 1.0c
first(int64)10M6.3x≥3x0.0020.002 CPU-ms4.782 1.0c0.009 1.1c0.033 1.0c—4.883 1.0c
sum by int32 key (100000 groups)50M6.3x≥3x7.4111.253 CPU-ms102.4 14.2c99.4 14.7c48.1 11.3c46.4 11.7c334.8 1.0c
fill_null_forward (int64)50M6.2x≥3x9.6271.742 CPU-ms137.3 1.0c138.0 1.0c59.2 1.0c—59.7 1.0c
sum by utf8 key (100000 distinct)50M6.1x≥3x23.83.816 CPU-ms178.0 12.9c184.1 13.5c144.6 12.3c144.6 12.0c782.8 1.0c
first by key (100000 groups)10M6.1x≥3x2.9421.967 CPU-ms17.9 11.2c18.0 11.2c54.4 1.0c—49.3 1.0c
last(int64)10M5.9x≥3x0.0020.003 CPU-ms4.823 1.0c0.009 1.0c0.037 1.0c—6.940 1.0c
assume_timezone (America/New_York)50M5.8x≥3x15.81.831 CPU-ms2,697 1.0c204.8 15.4c1,069 1.0c92.5 14.4c3,119 1.0c
first by key (100000 groups)50M5.8x≥3x11.33.384 CPU-ms65.6 14.6c69.2 13.6c260.6 1.0c—241.6 1.0c
sort utf810M5.8x≥3x23.07.129 CPU-ms133.1 10.0c134.7 9.5c1,865 1.0c2,486 1.0c1,867 1.0c
list by key (10000000 groups)50M5.7x≥3x139.35.772 CPU-ms797.3 5.5c794.4 5.4c1,487 2.4c1,433 2.5c85,183 1.0c
first(int64)50M5.6x≥3x0.0020.002 CPU-ms24.4 1.0c0.010 1.1c0.149 1.0c—23.4 1.0c
sum by utf8 key (100000 distinct)10M5.4x≥3x6.6053.701 CPU-ms35.8 11.4c35.8 12.1c39.9 6.1c47.8 7.6c157.9 1.0c
indices_nonzero (bool)50M5.4x≥3x3.1680.826 CPU-ms129.6 1.0c130.3 1.0c117.6 1.0c—17.0 numpy 1.0c
max by key, float64 (10000000 groups)10M5.3x≥3x14.41.729 CPU-ms77.1 13.4c80.9 13.3c300.7 2.1c311.5 2.0c321.5 1.0c
mean(int64, 10% nulls)50M5.3x≥3x1.1450.428 CPU-ms13.2 1.0c13.3 1.0c64.6 1.0c6.115 13.3c64.2 1.0c
max by int32 key (100000 groups)50M5.3x≥3x10.61.529 CPU-ms115.8 12.6c106.1 13.8c59.6 10.2c56.3 10.9c334.9 1.0c
min by key, float64 (10000000 groups)10M5.2x≥3x14.41.701 CPU-ms75.4 14.0c79.3 13.6c298.8 2.1c312.6 2.1c318.4 1.0c
mean(int64, 10% nulls)10M5.2x≥3x0.2860.321 CPU-ms2.385 1.0c2.426 1.0c11.6 1.0c1.497 10.9c11.5 1.0c
max by key, float64 (10000000 groups)50M5.2x≥3x63.01.935 CPU-ms327.4 14.2c328.8 14.2c1,241 3.1c1,228 3.1c1,343 1.0c
first by key (10000000 groups)50M5.1x≥3x65.63.870 CPU-ms336.1 11.6c344.8 11.5c1,146 1.0c—1,047 1.0c
min by key, float64 (10000000 groups)50M5.1x≥3x63.01.930 CPU-ms321.5 14.3c325.2 14.5c1,259 3.0c1,234 3.1c1,326 1.0c
decimal sum50M5.1x≥3x1.8140.443 CPU-ms92.1 1.0c11.4 9.5c95.0 1.0c9.211 12.8c93.9 1.0c
match_like("cust_1%") [_ wildcard]_ is LIKE's single-character wildcard, so this is not a pure prefix and takes the ICU host path10M5.1x≥3x1.6950.422 CPU-ms100.0 1.0c8.559 14.6c183.6 1.0c18.3 12.1c203.1 1.0c
max by int32 key (10000000 groups)10M5.0x≥3x15.01.817 CPU-ms75.5 13.8c79.6 13.4c309.1 2.0c321.4 2.2c324.8 1.0c
min by key, float64 (1000 groups)50M5.0x≥3x6.1111.720 CPU-ms139.0 14.5c88.9 13.2c41.8 12.0c30.5 12.7c224.8 1.0c
min by int32 key (100000 groups)50M4.9x≥3x10.61.505 CPU-ms100.4 14.5c103.4 14.3c52.7 11.1c52.1 11.2c335.0 1.0c
min by int32 key (10000000 groups)10M4.9x≥3x14.91.684 CPU-ms72.6 14.4c78.2 13.7c310.7 2.0c320.8 2.1c324.5 1.0c
pairwise_diff (float64)10M4.8x≥3x0.9370.582 CPU-ms4.516 1.0c4.666 1.0c21.0 1.0c—4.976 1.0c
replace_substring("cust" -> "cx")10M4.8x≥3x3.0491.259 CPU-ms300.4 1.0c23.4 15.1c140.9 1.0c14.7 11.7c141.4 1.0c
top_k (k=10000, int64)10M4.8x≥3x2.4741.856 CPU-ms11.8 1.0c11.9 1.0c12.0 1.0c—45.6 numpy 1.0c
sum by utf8 key (10000000 distinct)50M4.8x≥3x126.16.389 CPU-ms602.8 12.3c685.0 11.5c2,944 2.1c2,928 2.1c4,288 1.0c
fill_null_forward (int64)10M4.7x≥3x2.3411.361 CPU-ms27.2 1.0c27.6 1.0c11.5 1.0c—11.1 1.0c
max by key, float64 (1000 groups)50M4.7x≥3x6.0861.724 CPU-ms142.2 14.3c89.6 12.9c41.3 12.1c28.7 12.5c234.4 1.0c
concat (a + '-' + b)10M4.7x≥3x4.1571.496 CPU-ms312.3 1.0c20.2 14.3c201.2 1.0c19.5 12.7c363.6 1.0c
index_in (int64, 100-value set)50M4.7x≥3x5.9283.510 CPU-ms——332.4 1.0c27.7 14.2c163.0 numpy 1.0c
min by int32 key (10000000 groups)50M4.6x≥3x72.01.950 CPU-ms328.9 14.3c347.1 13.5c1,356 2.8c1,314 2.8c1,423 1.0c
max by int32 key (10000000 groups)50M4.5x≥3x72.12.009 CPU-ms334.0 14.2c326.5 14.2c1,346 2.8c1,346 2.8c1,402 1.0c
dictionary_encode (int32)50M4.4x≥3x19.17.095 CPU-ms——342.0 1.0c—84.2 1.0c
count_distinct by key (100000 groups)10M4.3x≥3x7.2772.330 CPU-ms31.6 13.0c31.0 13.3c363.8 2.1c375.6 2.4c869.5 1.0c
pad_left (width 20)10M4.2x≥3x3.1471.307 CPU-ms173.7 1.0c13.2 14.2c181.8 1.0c17.2 12.5c182.3 1.0c
sort float6450M4.1x≥3x32.11.253 CPU-ms132.9 9.7c133.1 9.7c6,044 1.0c7,498 1.0c1,759 numpy 1.0c
count by int32 key (1000 groups)50M4.1x≥3x4.2291.232 CPU-ms89.0 9.1c89.6 9.1c17.5 13.5c17.4 13.5c138.5 1.0c
is_dst (America/New_York)10M4.1x≥3x3.0830.418 CPU-ms388.0 1.0c29.5 15.1c110.1 1.0c12.6 10.7c—
variance by key (10000000 groups)50M4.1x≥3x79.43.063 CPU-ms323.0 14.4c322.9 14.7c2,444 4.8c2,438 4.8c1,368 1.0c
sum by utf8 key (10000000 distinct)10M4.1x≥3x27.55.817 CPU-ms111.9 11.9c112.7 11.8c572.0 1.8c581.4 2.0c801.9 1.0c
match_substring_regex (literal)10M4.0x≥3x2.8580.414 CPU-ms144.8 1.0c11.6 14.9c256.6 1.0c24.6 12.1c248.4 1.0c
sum by two int32 keys (~1024 groups)50M4.0x≥3x5.9132.115 CPU-ms164.2 13.5c177.1 12.7c23.9 13.5c24.0 13.5c448.4 1.0c
first by key (1000 groups)10M4.0x≥3x3.8212.240 CPU-ms15.2 10.6c15.1 10.8c42.4 1.0c—39.4 1.0c
char_length10M3.8x≥3x1.0960.387 CPU-ms52.6 1.0c4.217 14.8c52.3 1.0c5.447 11.6c52.5 1.0c
sort float6410M3.8x≥3x6.8541.168 CPU-ms26.2 9.6c26.3 9.5c1,006 1.0c1,159 1.0c303.1 numpy 1.0c
mean by int32 key (1000 groups)50M3.8x≥3x4.9141.240 CPU-ms81.5 14.5c82.1 14.4c18.7 13.6c18.6 13.5c264.6 1.0c
sum by int32 key (1000 groups)50M3.8x≥3x4.8881.227 CPU-ms82.4 14.5c81.9 14.5c18.5 13.5c18.5 13.6c247.9 1.0c
count_distinct by key (1000 groups)50M3.8x≥3x41.82.640 CPU-ms307.9 13.0c156.8 14.0c1,886 2.2c1,859 2.3c4,773 1.0c
match_substring_regex (literal)1M3.7x≥3x0.5130.354 CPU-ms14.5 1.0c1.921 9.3c25.2 1.0c2.726 11.3c25.4 1.0c
count_distinct by key (1000 groups)10M3.7x≥3x7.1272.332 CPU-ms26.6 13.1c26.7 13.1c327.0 2.0c323.1 2.1c854.6 1.0c
hash join indices (10000000 x 200000 on int64)the join kernel alone (am_join): the matching index pairs, no payload gather10M3.7x≥3x2.8360.788 CPU-ms22.0 9.5c22.6 9.2c10.4 14.1c10.4 14.3c102.9 1.0c
local_timestamp (America/New_York)10M3.6x≥3x3.2780.686 CPU-ms493.8 1.0c37.2 15.1c100.9 1.0c12.0 10.7c224.5 1.0c
product(int64)50M3.6x≥3x1.3630.428 CPU-ms48.5 1.0c48.4 1.0c50.6 1.0c4.960 13.8c50.8 1.0c
variance by key (10000000 groups)10M3.6x≥3x20.42.834 CPU-ms73.4 13.9c78.8 13.5c460.1 2.4c462.4 2.4c347.6 1.0c
shift_left (int64 << 2)50M3.6x≥3x1.8940.393 CPU-ms——53.6 1.0c6.812 11.8c6.729 numpy 1.0c
stddev by key (10000000 groups)50M3.5x≥3x93.215.7 CPU-ms328.9 14.1c326.8 14.6c2,443 4.8c2,453 4.8c1,370 1.0c
is_dst (America/New_York)50M3.5x≥3x14.20.443 CPU-ms1,938 1.0c144.1 15.4c544.6 1.0c49.4 13.7c—
hash join indices (50000000 x 1000000 on int64)the join kernel alone (am_join): the matching index pairs, no payload gather50M3.5x≥3x18.30.805 CPU-ms107.1 9.7c110.4 9.6c63.3 15.1c64.7 15.0c589.3 0.9c
count_distinct by key (100000 groups)50M3.4x≥3x42.32.672 CPU-ms161.8 14.6c142.5 15.3c2,028 2.4c2,029 2.4c4,674 1.0c
tdigest(float64, q=0.5)10M3.3x≥3x7.0501.703 CPU-ms——195.2 1.0c23.6 9.9c—
struct_field10M3.3x≥3x0.0010.003 CPU-ms0.013 1.1c0.210 3.9c0.003 1.3c0.590 10.7c0.014 1.1c
min by int32 key (1000 groups)50M3.3x≥3x5.9151.545 CPU-ms82.5 14.2c80.6 14.8c19.7 13.8c19.6 13.9c270.6 1.0c
max by int32 key (1000 groups)50M3.3x≥3x5.9111.593 CPU-ms83.4 14.4c83.2 14.4c19.8 13.6c19.5 13.7c273.5 1.0c
list_flatten10M3.1x≥3x0.0010.002 CPU-ms19.8 1.0c19.3 1.0c0.002 0.9c—15.6 1.0c
decimal sum10M3.1x≥3x0.6050.374 CPU-ms18.4 1.0c2.340 10.0c19.8 1.0c1.880 12.7c19.7 1.0c
list by key (10000000 groups)10M3.1x≥3x60.04.482 CPU-ms186.0 4.6c190.6 4.7c306.3 1.9c317.8 1.9c49,627 1.0c
product(int64)10M3.1x≥3x0.4300.370 CPU-ms9.388 1.0c8.954 1.0c9.878 1.0c1.327 10.6c9.750 1.0c
mode(int64)50M3.1x≥3x33.68.560 CPU-ms142.8 13.4c132.2 14.6c103.7 1.0c—342.3 1.0c
left outer hash join (10000000 x 200000 on int64)am_join join_type 1: index pairs with a null right index for an unmatched left row10M3.1x≥3x3.0451.026 CPU-ms57.9 5.2c57.7 5.2c9.390 14.0c9.517 14.0c252.8 1.0c
left outer hash join (50000000 x 1000000 on int64)am_join join_type 1: index pairs with a null right index for an unmatched left row50M3.0x≥3x19.11.331 CPU-ms285.0 5.0c286.3 4.9c58.0 14.8c58.1 14.8c1,690 1.0c
all(bool, 10% nulls)host short-circuit: any/all stop at the first set bit; no GPU dispatch, so this is a CPU scan and the ratio is data-dependent10M19.2x≥3x0.0020.002 CPU-ms0.105 1.0c0.114 1.0c0.029 1.0c0.361 9.7c0.033 1.0c
all(bool, 10% nulls)host short-circuit: any/all stop at the first set bit; no GPU dispatch, so this is a CPU scan and the ratio is data-dependent50M106.4x≥3x0.0010.003 CPU-ms0.773 1.0c0.591 1.0c0.149 1.0c1.436 14.1c0.154 1.0c
any(bool, 10% nulls)host short-circuit: any/all stop at the first set bit; no GPU dispatch, so this is a CPU scan and the ratio is data-dependent10M23.8x≥3x0.0010.003 CPU-ms0.098 1.0c0.108 1.0c0.029 1.0c0.360 9.7c0.033 1.0c
any(bool, 10% nulls)host short-circuit: any/all stop at the first set bit; no GPU dispatch, so this is a CPU scan and the ratio is data-dependent50M106.4x≥3x0.0010.002 CPU-ms0.544 1.0c0.555 1.0c0.149 1.0c1.414 13.9c0.154 1.0c
inner hash join (50000000 x 1000000 on int64)am.join index pairs, then one take per output column50M2.9x1–3x21.81.567 CPU-ms105.8 10.0c106.6 10.0c63.9 15.0c64.7 14.9c542.9 1.0c
count by int32 key (1000 groups)10M2.9x1–3x1.5161.026 CPU-ms21.8 7.5c21.2 7.8c4.562 9.2c4.425 10.9c27.5 1.0c
lower10M2.9x1–3x5.3501.344 CPU-ms373.1 1.0c29.3 14.5c172.2 1.0c15.6 12.7c172.0 1.0c
upperre-measured in place after the full run flagged it (same build, quieter machine)10M2.9x1–3x5.3231.378 CPU-ms295.6 1.0c25.1 14.2c168.2 1.0c15.3 12.9c168.6 1.0c
min by key, float64 (1000 groups)10M2.8x1–3x1.7441.407 CPU-ms19.8 12.0c20.2 12.0c5.055 9.2c4.863 11.0c49.3 1.0c
exp (float32)50M2.7x1–3x1.9270.450 CPU-ms63.3 1.0c5.294 13.4c55.9 1.0c6.422 12.6c61.9 numpy 1.0c
contains("north")1M2.7x1–3x0.5090.349 CPU-ms14.4 1.0c1.923 9.2c12.3 1.0c1.394 11.0c11.6 1.0c
local_timestamp (America/New_York)50M2.7x1–3x15.51.839 CPU-ms2,461 1.0c183.6 15.5c508.1 1.0c42.5 15.3c1,122 1.0c
char_length1M2.7x1–3x0.2290.273 CPU-ms5.089 1.0c0.802 8.3c5.202 1.0c0.621 10.2c5.277 1.0c
decimal multiply (by scalar)50M2.7x1–3x3.5880.405 CPU-ms458.6 1.0c35.5 15.1c74.1 1.0c9.638 12.4c75.5 1.0c
inner hash join (10000000 x 200000 on int64)am.join index pairs, then one take per output column10M2.7x1–3x3.8831.536 CPU-ms22.1 9.5c22.5 9.4c10.4 14.1c10.6 14.1c96.2 1.0c
sum by int32 key (1000 groups)10M2.7x1–3x1.7341.034 CPU-ms20.3 11.4c20.2 12.0c4.803 9.2c4.613 11.0c45.5 1.0c
top_k (k=100, int64)10M2.6x1–3x1.8341.328 CPU-ms11.7 1.0c11.9 1.0c4.845 1.0c—45.2 numpy 1.0c
stddev by key (100000 groups)50M2.5x1–3x38.12.896 CPU-ms123.6 14.6c95.8 14.6c235.0 11.7c167.1 11.6c424.5 1.0c
variance by key (100000 groups)50M2.5x1–3x37.72.470 CPU-ms98.1 14.8c94.9 14.7c239.9 11.8c161.6 11.5c421.5 1.0c
sum by two int32 keys (~1024 groups)10M2.5x1–3x2.1901.769 CPU-ms34.0 11.3c35.1 11.3c5.652 9.2c5.497 11.0c84.1 1.0c
slice_codeunits [5:10]10M2.5x1–3x2.7901.213 CPU-ms86.7 1.0c6.964 14.4c70.5 1.0c7.441 11.6c70.4 1.0c
split_pattern("_")10M2.4x1–3x8.4672.563 CPU-ms263.3 1.0c25.4 12.9c224.2 1.0c20.7 13.0c223.1 1.0c
strftime (%Y-%m-%d)10M2.4x1–3x30.81.390 CPU-ms941.1 1.0c75.0 14.9c2,158 1.0c174.8 15.0c935.6 1.0c
variance by key (100000 groups)10M2.4x1–3x9.4662.186 CPU-ms23.0 12.2c23.5 12.2c39.4 6.6c46.5 7.8c84.3 1.0c
tdigest(float64, q=0.5)50M2.4x1–3x33.22.025 CPU-ms——1,061 1.0c80.0 15.3c—
strftime (%Y-%m-%d)50M2.4x1–3x151.12.095 CPU-ms4,709 1.0c361.5 15.5c10,645 1.0c839.0 15.4c4,629 1.0c
dictionary_encode (int32)10M2.4x1–3x7.0825.724 CPU-ms——71.2 1.0c—16.9 1.0c
stddev by key (100000 groups)10M2.4x1–3x9.6562.390 CPU-ms23.7 12.1c23.0 12.4c39.1 6.7c46.8 7.6c84.8 1.0c
sum by utf8 key (1000 distinct)50M2.2x1–3x18.23.460 CPU-ms144.0 13.4c142.4 13.7c40.5 13.9c40.1 14.1c554.5 1.0c
is_alpha10M2.2x1–3x2.0860.542 CPU-ms85.6 1.0c7.267 14.1c46.2 1.0c4.580 11.8c47.7 1.0c
kurtosis(float64)10M2.2x1–3x2.5160.784 CPU-ms10.7 1.0c24.5 1.0c48.2 1.0c5.502 11.5c17.6 1.0c
variance(float64, ddof=1)50M2.2x1–3x5.4460.788 CPU-ms52.6 1.0c52.0 1.0c127.9 1.0c11.9 13.3c127.9 1.0c
stddev(float64, ddof=1)50M2.2x1–3x5.4910.826 CPU-ms52.7 1.0c52.2 1.0c128.0 1.0c11.9 13.2c128.1 1.0c
indices_nonzero (bool)10M2.1x1–3x1.5900.784 CPU-ms24.0 1.0c24.0 1.0c22.6 1.0c—3.402 numpy 1.0c
trim (whitespace)10M2.1x1–3x5.0661.319 CPU-ms144.2 1.0c10.8 14.8c139.0 1.0c13.6 12.0c139.0 1.0c
kurtosis(float64)50M2.1x1–3x10.90.846 CPU-ms58.7 1.0c132.5 1.0c258.7 1.0c22.9 13.7c92.4 1.0c
mean by int32 key (1000 groups)10M2.1x1–3x2.2071.171 CPU-ms20.3 11.4c20.4 11.5c4.808 9.2c4.633 11.0c49.7 1.0c
sum(int64, 10% nulls)50M2.1x1–3x1.1110.405 CPU-ms15.9 1.0c2.304 11.1c52.1 1.0c5.091 13.0c51.5 1.0c
stddev by key (10000000 groups)10M2.1x1–3x37.018.8 CPU-ms76.6 13.5c79.8 13.8c460.6 2.4c463.7 2.3c351.8 1.0c
decimal add (128-bit)50M2.0x1–3x5.4180.407 CPU-ms275.7 1.0c21.6 14.1c74.1 1.0c11.0 12.8c74.4 1.0c
fill_null (int64)50M2.0x1–3x2.1070.421 CPU-ms24.3 1.0c4.165 12.9c41.8 1.0c6.439 11.9c42.0 1.0c
count_distinct(int64)50M2.0x1–3x9.6072.322 CPU-ms251.1 2.4c251.9 2.4c200.4 1.0c18.9 13.1c204.5 1.0c
multiply scalar (int64)50M2.0x1–3x2.1020.400 CPU-ms24.8 1.0c4.132 12.6c13.0 1.0c4.264 11.5c54.9 1.0c
mode(int64)10M2.0x1–3x10.0007.463 CPU-ms28.7 13.4c32.2 11.7c19.5 1.0c—66.8 1.0c
add_checked (int64 + int64)50M1.9x1–3x4.4970.642 CPU-ms——75.7 1.0c8.631 12.1c—
sum(int64, 10% nulls)10M1.9x1–3x0.3030.337 CPU-ms3.035 1.0c0.575 10.5c9.342 1.0c1.294 10.7c9.155 1.0c
max(int64, 10% nulls)50M1.9x1–3x1.1720.497 CPU-ms13.7 1.0c2.218 10.1c50.5 1.0c5.083 13.4c50.6 1.0c
week10M1.9x1–3x5.0280.707 CPU-ms111.6 1.0c9.496 14.4c126.1 1.0c13.9 10.7c284.6 1.0c
week50M1.9x1–3x24.21.841 CPU-ms557.3 1.0c45.0 15.3c587.4 1.0c56.0 13.4c1,391 1.0c
list_value_length10M1.8x1–3x0.3610.372 CPU-ms5.468 1.0c0.733 10.2c0.730 1.0c0.659 9.6c0.788 1.0c
multiply scalar (int64)re-measured in place after the full run flagged it (same build, quieter machine)10M1.8x1–3x0.4820.356 CPU-ms4.589 1.0c0.875 11.8c2.616 1.0c1.049 9.4c10.3 1.0c
match_like("cust%") [pure prefix]a pure prefix pattern, which Regex.likePredicate maps onto the GPU starts_with kernel10M1.8x1–3x1.1490.422 CPU-ms23.0 1.0c2.065 12.8c18.9 1.0c2.115 11.1c185.8 1.0c
coalesce (2 int64 columns)50M1.8x1–3x3.3150.413 CPU-ms55.3 1.0c5.887 14.0c74.5 1.0c8.480 12.5c146.5 1.0c
decimal add (128-bit)10M1.8x1–3x1.4360.394 CPU-ms55.3 1.0c4.356 13.7c15.0 1.0c2.538 10.1c15.0 1.0c
add_checked (int64 + int64)10M1.7x1–3x1.1670.504 CPU-ms——15.2 1.0c2.037 10.1c—
concat (a + '-' + b)1M1.7x1–3x1.2461.157 CPU-ms30.4 1.0c2.611 11.0c20.1 1.0c2.138 10.9c37.1 1.0c
min(int64, 10% nulls)re-measured in place after the full run flagged it (same build, quieter machine)50M1.7x1–3x1.1830.554 CPU-ms13.4 1.0c2.015 10.1c47.2 1.0c5.054 13.3c47.7 1.0c
count_distinct(int64)10M1.7x1–3x2.8722.054 CPU-ms48.9 2.6c49.1 2.5c37.1 1.0c4.830 10.4c38.7 1.0c
match_like("cust_1%") [_ wildcard]_ is LIKE's single-character wildcard, so this is not a pure prefix and takes the ICU host path1M1.7x1–3x0.9460.419 CPU-ms10.1 1.0c1.582 8.4c18.3 1.0c2.024 11.1c20.5 1.0c
index_in (int64, 100-value set)10M1.7x1–3x4.0312.629 CPU-ms——62.5 1.0c6.696 11.7c32.9 numpy 1.0c
sum by utf8 key (1000 distinct)10M1.7x1–3x5.6613.371 CPU-ms30.9 10.7c32.7 12.2c9.396 9.3c9.480 10.7c104.5 1.0c
is_in (int64, 100-value set)re-measured in place after the full run flagged it (same build, quieter machine)10M1.7x1–3x2.2081.620 CPU-ms43.7 1.0c3.646 12.3c74.3 1.0c8.145 11.9c78.5 1.0c
skew(float64)50M1.6x1–3x10.80.796 CPU-ms50.2 1.0c127.7 1.0c193.1 1.0c17.6 13.2c95.9 1.0c
skew(float64)10M1.6x1–3x2.4820.771 CPU-ms9.476 1.0c24.5 1.0c36.0 1.0c4.039 11.7c18.4 1.0c
multiply array (int64 * int64)10M1.6x1–3x0.7130.419 CPU-ms5.214 1.0c1.137 11.3c3.102 1.0c1.211 10.3c28.3 1.0c
add array (int64 + int64)10M1.6x1–3x0.6840.406 CPU-ms5.133 1.0c1.085 11.4c2.904 1.0c1.228 10.8c28.3 1.0c
is_nan (float64)50M1.6x1–3x1.2100.379 CPU-ms4.872 1.0c1.916 13.0c9.664 1.0c3.497 11.0c4.938 numpy 1.0c
starts_with("cust_1")10M1.6x1–3x1.8120.406 CPU-ms35.3 1.0c2.841 14.6c31.0 1.0c3.392 11.1c31.0 1.0c
add_duration (+1h)50M1.6x1–3x2.0890.383 CPU-ms25.3 1.0c3.260 12.7c13.2 1.0c4.367 12.0c182.7 1.0c
decimal compare (> scalar)50M1.6x1–3x2.1970.405 CPU-ms14.9 1.0c3.411 14.0c41.9 1.0c5.506 12.8c42.1 1.0c
add scalar (int64)50M1.5x1–3x1.9470.414 CPU-ms23.3 1.0c2.997 12.1c13.3 1.0c4.444 11.3c51.9 1.0c
round (float64)50M1.5x1–3x3.1790.406 CPU-ms51.1 1.0c4.725 9.6c112.1 1.0c11.5 12.3c110.1 1.0c
is_in (utf8, 100-value set)10M1.5x1–3x3.8152.000 CPU-ms96.0 1.0c5.663 13.6c104.8 1.0c10.6 11.7c109.4 1.0c
add_duration (+1h)10M1.5x1–3x0.5410.373 CPU-ms4.710 1.0c0.800 9.8c2.663 1.0c1.005 9.8c37.6 1.0c
pad_left (width 20)1M1.5x1–3x1.2981.070 CPU-ms16.9 1.0c2.051 9.6c18.3 1.0c1.895 11.6c18.4 1.0c
bit_wise_and (int64)50M1.5x1–3x2.9660.626 CPU-ms28.0 1.0c4.321 13.3c86.6 1.0c9.372 12.5c10.1 numpy 1.0c
multiply array (int64 * int64)50M1.5x1–3x2.9640.619 CPU-ms27.8 1.0c4.318 13.8c15.1 1.0c5.909 11.4c147.5 1.0c
variance(float64, ddof=1)10M1.4x1–3x1.9310.797 CPU-ms9.603 1.0c9.816 1.0c23.7 1.0c2.797 11.4c23.8 1.0c
negate (int64)50M1.4x1–3x2.1440.403 CPU-ms23.4 1.0c3.095 13.0c13.3 1.0c3.975 10.8c52.5 1.0c
hash64 (int64)50M1.4x1–3x3.2000.418 CPU-ms55.4 1.0c4.537 13.2c——79.8 1.0c
if_else (bool ? int64 : int64)50M1.4x1–3x3.7450.958 CPU-ms27.2 1.0c5.307 13.6c122.7 1.0c12.7 12.5c14.6 numpy 1.0c
add scalar (int64)10M1.4x1–3x0.5470.373 CPU-ms4.352 1.0c0.775 10.1c2.703 1.0c1.013 10.5c10.0 1.0c
stddev(float64, ddof=1)10M1.4x1–3x1.9230.778 CPU-ms9.767 1.0c9.531 1.0c23.4 1.0c2.704 11.8c23.9 1.0c
max by int32 key (1000 groups)10M1.4x1–3x3.5251.460 CPU-ms19.9 11.9c20.7 11.7c5.087 9.2c4.942 10.9c53.1 1.0c
add array (int64 + int64)50M1.4x1–3x3.1230.614 CPU-ms27.0 1.0c4.313 12.8c15.2 1.0c5.643 12.0c147.5 1.0c
min_max(int64)50M1.3x1–3x3.7651.598 CPU-ms27.4 1.0c14.1 2.7c50.6 1.0c5.074 13.3c101.4 1.0c
decimal multiply (by scalar)10M1.3x1–3x1.6040.399 CPU-ms91.0 1.0c7.410 14.4c14.9 1.0c2.161 10.4c15.2 1.0c
min by int32 key (1000 groups)10M1.3x1–3x3.7081.642 CPU-ms20.3 11.9c20.5 11.4c5.060 9.2c4.989 10.9c52.4 1.0c
compare scalar (int64 > 0)10M1.3x1–3x0.4710.381 CPU-ms0.875 1.0c0.610 8.0c1.200 1.0c0.843 9.7c1.283 1.0c
max by key, float64 (1000 groups)10M1.3x1–3x3.7651.626 CPU-ms19.6 11.9c20.1 11.7c4.998 9.2c4.834 11.0c51.0 1.0c
bit_wise_and (int64)10M1.3x1–3x0.8560.432 CPU-ms5.053 1.0c1.098 11.2c16.5 1.0c2.137 10.3c1.856 numpy 1.0c
filter int64 (30% kept)re-measured in place after the full run flagged it (same build, quieter machine)50M1.3x1–3x2.2290.673 CPU-ms17.5 1.0c2.845 12.6c122.7 1.0c22.6 13.2c157.0 1.0c
starts_with("cust_1")1M1.3x1–3x0.3410.352 CPU-ms