Skip to content

Commit 0d4b15a

Browse files
robert3005claude
andcommitted
ci(python): run Python benchmarks with CodSpeed
Run the existing vortex-python benchmark suite under pytest-codspeed in simulation mode, so CodSpeed tracks read and Arrow export speed through the Python bindings. pytest-codspeed rejects the `disable_gc` marker option. The benchmarks no longer set it, and the pytest-benchmark runs pass `--benchmark-disable-gc` instead. Signed-off-by: Robert Kruszewski <github@robertk.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Suq4jdU4jCnGsJeRRtGMF5
1 parent 751e0af commit 0d4b15a

10 files changed

Lines changed: 90 additions & 22 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ jobs:
149149

150150
- name: Pytest Benchmarks - Vortex
151151
run: |
152-
bash ../scripts/bench-taskset.sh uv run --all-packages pytest --benchmark-only benchmark/
152+
bash ../scripts/bench-taskset.sh uv run --all-packages pytest --benchmark-only --benchmark-disable-gc benchmark/
153153
working-directory: vortex-python/
154154

155155
python-windows:

‎.github/workflows/codspeed.yml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ jobs:
126126
# Cached package restoration fails on dangling libc6-dbg documentation symlinks.
127127
cache-instruments: "false"
128128

129+
bench-codspeed-python:
130+
name: "Benchmark with Codspeed (Python)"
131+
timeout-minutes: 45
132+
runs-on: >-
133+
${{ github.repository == 'vortex-data/vortex'
134+
&& format('runs-on={0}/runner=amd64-medium/image=ubuntu24-full-x64-pre-v2/extras=s3-cache/tag=bench-codspeed-python', github.run_id)
135+
|| 'ubuntu-latest' }}
136+
env:
137+
# The bench profile keeps the debug info that CodSpeed uses to attribute native frames.
138+
MATURIN_PEP517_ARGS: "--profile bench"
139+
steps:
140+
- uses: runs-on/action@v2
141+
if: github.repository == 'vortex-data/vortex'
142+
with:
143+
sccache: s3
144+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
145+
- uses: ./.github/actions/setup-prebuild
146+
with:
147+
enable-sccache: ${{ github.repository == 'vortex-data/vortex' && 'true' || 'false' }}
148+
- uses: ./.github/actions/system-info
149+
# Build the extension before measurement, so the benchmark step does not compile it.
150+
- name: Build Python extension
151+
run: uv sync --all-packages
152+
- name: Run benchmarks
153+
uses: CodSpeedHQ/action@f842b9a3ffe213dbb81768fd7f7eeb4dfcf1c369 # v5
154+
with:
155+
run: cd vortex-python && uv run --all-packages --no-sync pytest benchmark/ --codspeed
156+
token: ${{ secrets.CODSPEED_TOKEN }}
157+
mode: "simulation"
158+
# Cached package restoration fails on dangling libc6-dbg documentation symlinks.
159+
cache-instruments: "false"
160+
129161
# Compile on smaller VMs from the same CPU family as the measurement hosts. Globally
130162
# enabled features also affect build scripts, so the build host must support them.
131163
bench-codspeed-cpu-features-build:

‎uv.lock‎

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vortex-python/benchmark/test_aggregation.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,27 @@ def _has_mean(t: pa.DataType) -> bool:
1616
return is_integer(t) or is_floating(t)
1717

1818

19-
@pytest.mark.benchmark(group="aggregation", disable_gc=True)
19+
@pytest.mark.benchmark(group="aggregation")
2020
def test_arrow_table_aggregation(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2121
aggregations: list[tuple[str, Literal["mean"]]] = [
2222
(field.name, "mean") for field in vxf.dtype.to_arrow_schema() if _has_mean(field.type)
2323
]
2424
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in vxf.scan()).group_by([]).aggregate(aggregations))
2525

2626

27-
@pytest.mark.benchmark(group="aggregation", disable_gc=True)
27+
@pytest.mark.benchmark(group="aggregation")
2828
def test_polars_aggregation(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2929
lf = vxf.to_polars()
3030
benchmark(lambda: lf.mean().collect().to_arrow())
3131

3232

33-
@pytest.mark.benchmark(group="aggregation", disable_gc=True)
33+
@pytest.mark.benchmark(group="aggregation")
3434
def test_polars_streaming_aggregation(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3535
lf = vxf.to_polars()
3636
benchmark(lambda: lf.mean().collect(engine="streaming").to_arrow())
3737

3838

39-
@pytest.mark.benchmark(group="aggregation", disable_gc=True)
39+
@pytest.mark.benchmark(group="aggregation")
4040
def test_duckdb_aggregation(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
4141
conn = duckdb.connect(database=":memory:")
4242
ds = vxf.to_dataset()

‎vortex-python/benchmark/test_filter.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,30 @@
1111
from vortex.expr import column
1212

1313

14-
@pytest.mark.benchmark(group="filter", disable_gc=True)
14+
@pytest.mark.benchmark(group="filter")
1515
def test_scan_filter(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
1616
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in vxf.scan(expr=column("x") >= 50_000)))
1717

1818

19-
@pytest.mark.benchmark(group="filter", disable_gc=True)
19+
@pytest.mark.benchmark(group="filter")
2020
def test_repeated_scan_filter(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2121
rscan = vxf.to_repeated_scan(expr=column("x") > 50_000)
2222
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in rscan.execute()))
2323

2424

25-
@pytest.mark.benchmark(group="filter", disable_gc=True)
25+
@pytest.mark.benchmark(group="filter")
2626
def test_polars_filter(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2727
lf = vxf.to_polars()
2828
benchmark(lambda: lf.filter(pl.col("x") >= pl.lit(50_000).cast(pl.Int64)).collect().to_arrow())
2929

3030

31-
@pytest.mark.benchmark(group="filter", disable_gc=True)
31+
@pytest.mark.benchmark(group="filter")
3232
def test_polars_streaming_filter(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3333
lf = vxf.to_polars()
3434
benchmark(lambda: lf.filter(pl.col("x") >= pl.lit(50_000).cast(pl.Int64)).collect(engine="streaming").to_arrow())
3535

3636

37-
@pytest.mark.benchmark(group="filter", disable_gc=True)
37+
@pytest.mark.benchmark(group="filter")
3838
def test_duckdb_filter(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3939
conn = duckdb.connect(database=":memory:")
4040
ds = vxf.to_dataset()

‎vortex-python/benchmark/test_scalar_at.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,30 @@
99
import vortex as vx
1010

1111

12-
@pytest.mark.benchmark(group="scalar_at", disable_gc=True)
12+
@pytest.mark.benchmark(group="scalar_at")
1313
def test_scan_scalar_at(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
1414
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in vxf.scan(indices=vx.array([50_000]))))
1515

1616

17-
@pytest.mark.benchmark(group="scalar_at", disable_gc=True)
17+
@pytest.mark.benchmark(group="scalar_at")
1818
def test_repeated_scan_scalar_at(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
1919
rscan = vxf.to_repeated_scan()
2020
benchmark(lambda: rscan.scalar_at(50_000))
2121

2222

23-
@pytest.mark.benchmark(group="scalar_at", disable_gc=True)
23+
@pytest.mark.benchmark(group="scalar_at")
2424
def test_polars_scalar_at(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2525
lf = vxf.to_polars()
2626
benchmark(lambda: lf.slice(50_000, 50_001).collect().to_arrow())
2727

2828

29-
@pytest.mark.benchmark(group="scalar_at", disable_gc=True)
29+
@pytest.mark.benchmark(group="scalar_at")
3030
def test_polars_streaming_scalar_at(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3131
lf = vxf.to_polars()
3232
benchmark(lambda: lf.slice(50_000, 50_001).collect(engine="streaming").to_arrow())
3333

3434

35-
@pytest.mark.benchmark(group="scalar_at", disable_gc=True)
35+
@pytest.mark.benchmark(group="scalar_at")
3636
def test_duckdb_scalar_at(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3737
conn = duckdb.connect(database=":memory:")
3838
ds = vxf.to_dataset()

‎vortex-python/benchmark/test_scan.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,30 @@
99
import vortex as vx
1010

1111

12-
@pytest.mark.benchmark(group="scan", disable_gc=True)
12+
@pytest.mark.benchmark(group="scan")
1313
def test_scan(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
1414
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in vxf.scan()))
1515

1616

17-
@pytest.mark.benchmark(group="scan", disable_gc=True)
17+
@pytest.mark.benchmark(group="scan")
1818
def test_repeated_scan(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
1919
rscan = vxf.to_repeated_scan()
2020
benchmark(lambda: pa.concat_tables(x.to_arrow_table() for x in rscan.execute()))
2121

2222

23-
@pytest.mark.benchmark(group="scan", disable_gc=True)
23+
@pytest.mark.benchmark(group="scan")
2424
def test_polars(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
2525
lf = vxf.to_polars()
2626
benchmark(lambda: lf.collect().to_arrow())
2727

2828

29-
@pytest.mark.benchmark(group="scan", disable_gc=True)
29+
@pytest.mark.benchmark(group="scan")
3030
def test_polars_streaming(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3131
lf = vxf.to_polars()
3232
benchmark(lambda: lf.collect(engine="streaming").to_arrow())
3333

3434

35-
@pytest.mark.benchmark(group="scan", disable_gc=True)
35+
@pytest.mark.benchmark(group="scan")
3636
def test_duckdb(benchmark: BenchmarkFixture, vxf: vx.VortexFile) -> None:
3737
conn = duckdb.connect(database=":memory:")
3838
ds = vxf.to_dataset()

‎vortex-python/benchmark/test_serialization.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
@pytest.mark.parametrize("protocol", [4, 5], ids=lambda p: f"p{p}")
1414
@pytest.mark.parametrize("operation", ["dumps", "loads", "roundtrip"])
15-
@pytest.mark.benchmark(disable_gc=True)
1615
def test_pickle(
1716
benchmark: BenchmarkFixture,
1817
array_fixture: vx.Array,

‎vortex-python/pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dev = [
8585
"polars>=1.32.0",
8686
"pyarrow-stubs>=17.16",
8787
"pytest-benchmark>=5.1.0",
88+
"pytest-codspeed>=5.0.3",
8889
"pytest-xdist>=3.5.0",
8990
"ray>=2.48",
9091
# forced transitive bumps

‎vortex-python/run_benchmarks.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ source "$ROOT/.venv/bin/activate"
2525
maturin develop --release --manifest-path "$PYTHON_DIR/Cargo.toml"
2626

2727
# Run benchmarks. Extra args are forwarded to pytest.
28-
pytest "$PYTHON_DIR/benchmark" "$@"
28+
pytest --benchmark-disable-gc "$PYTHON_DIR/benchmark" "$@"

0 commit comments

Comments
 (0)