From 2e09d0eb907f77108d44ceb7e65359be832f3537 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:33:06 +0000 Subject: [PATCH 1/6] [Autoloop: perf-comparison] Iteration 418: firstValidIndex/lastValidIndex benchmark Run: https://github.com/githubnext/tsb/actions/runs/30010910150 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_first_last_valid_index.py | 45 ++++++++++++++++ .../tsb/bench_first_last_valid_index.ts | 52 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 benchmarks/pandas/bench_first_last_valid_index.py create mode 100644 benchmarks/tsb/bench_first_last_valid_index.ts diff --git a/benchmarks/pandas/bench_first_last_valid_index.py b/benchmarks/pandas/bench_first_last_valid_index.py new file mode 100644 index 00000000..674aab50 --- /dev/null +++ b/benchmarks/pandas/bench_first_last_valid_index.py @@ -0,0 +1,45 @@ +""" +Benchmark: first_valid_index / last_valid_index +Outputs JSON: {"function": "first_last_valid_index", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +import pandas as pd + +N = 100_000 + +# Series where first valid is near the start (a few NaN at beginning) +data_start = np.where(np.arange(N) < 10, np.nan, np.arange(N, dtype=float)) +series_start = pd.Series(data_start) + +# Series where last valid is near the end (a few NaN at the end) +data_end = np.where(np.arange(N) >= N - 10, np.nan, np.arange(N, dtype=float)) +series_end = pd.Series(data_end) + +# Series with NaN scattered throughout +data_mixed = np.where(np.arange(N) % 7 == 0, np.nan, np.arange(N, dtype=float)) +series_mixed = pd.Series(data_mixed) + +# Warm-up +for _ in range(20): + series_start.first_valid_index() + series_end.last_valid_index() + series_mixed.first_valid_index() + series_mixed.last_valid_index() + +iterations = 500 +start = time.perf_counter() +for _ in range(iterations): + series_start.first_valid_index() + series_end.last_valid_index() + series_mixed.first_valid_index() + series_mixed.last_valid_index() +total_ms = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "first_last_valid_index", + "mean_ms": total_ms / iterations, + "iterations": iterations, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_first_last_valid_index.ts b/benchmarks/tsb/bench_first_last_valid_index.ts new file mode 100644 index 00000000..33d04869 --- /dev/null +++ b/benchmarks/tsb/bench_first_last_valid_index.ts @@ -0,0 +1,52 @@ +/** + * Benchmark: firstValidIndex / lastValidIndex + * Outputs JSON: {"function": "first_last_valid_index", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { Series, firstValidIndex, lastValidIndex } from "../../src/index.ts"; + +const N = 100_000; + +// Series where first valid is near the start (a few NaN/null at beginning) +const dataStart = Float64Array.from({ length: N }, (_, i) => + i < 10 ? NaN : i, +); +const seriesStart = new Series({ data: dataStart }); + +// Series where last valid is near the end (a few NaN/null at the end) +const dataEnd = Float64Array.from({ length: N }, (_, i) => + i >= N - 10 ? NaN : i, +); +const seriesEnd = new Series({ data: dataEnd }); + +// Series with NaN scattered throughout (worst-case scan) +const dataMixed = Float64Array.from({ length: N }, (_, i) => + i % 7 === 0 ? NaN : i, +); +const seriesMixed = new Series({ data: dataMixed }); + +// Warm-up +for (let w = 0; w < 20; w++) { + firstValidIndex(seriesStart); + lastValidIndex(seriesEnd); + firstValidIndex(seriesMixed); + lastValidIndex(seriesMixed); +} + +const iterations = 500; +const start = performance.now(); +for (let i = 0; i < iterations; i++) { + firstValidIndex(seriesStart); + lastValidIndex(seriesEnd); + firstValidIndex(seriesMixed); + lastValidIndex(seriesMixed); +} +const total_ms = performance.now() - start; + +console.log( + JSON.stringify({ + function: "first_last_valid_index", + mean_ms: total_ms / iterations, + iterations, + total_ms, + }), +); From 3dc825db15d933035463639253588e2b104b3704 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Jul 2026 13:33:10 +0000 Subject: [PATCH 2/6] ci: trigger checks From 402ebd60e22e5fa960c32897b16b6bfc6906e568 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 01:28:41 +0000 Subject: [PATCH 3/6] [Autoloop: perf-comparison] Iteration 419: BooleanArray benchmark Benchmark BooleanArray extension array (100k elements, ~10% nulls, 50 iters): from/any/all/sum/and/or/not/fillna. TS: arrays.BooleanArray from tsb; Python: pd.array(dtype='boolean'). Run: https://github.com/githubnext/tsb/actions/runs/30058807058 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_boolean_array.py | 43 ++++++++++++++++++++++ benchmarks/tsb/bench_boolean_array.ts | 46 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 benchmarks/pandas/bench_boolean_array.py create mode 100644 benchmarks/tsb/bench_boolean_array.ts diff --git a/benchmarks/pandas/bench_boolean_array.py b/benchmarks/pandas/bench_boolean_array.py new file mode 100644 index 00000000..8a16b8d8 --- /dev/null +++ b/benchmarks/pandas/bench_boolean_array.py @@ -0,0 +1,43 @@ +"""Benchmark: BooleanArray — nullable boolean extension array operations. +N=100_000 elements with ~10% nulls using pandas BooleanArray. +Tests: array creation, any, all, sum, and, or, invert, fillna. +""" +import json +import time +import pandas as pd + +N = 100_000 +WARMUP = 5 +ITERATIONS = 50 + +# Same pattern as TS version (~10% nulls) +raw = [(None if i % 10 == 0 else bool(i % 3 != 0)) for i in range(N)] +raw2 = [(None if i % 7 == 0 else bool(i % 2 == 0)) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="boolean") + b = pd.array(raw2, dtype="boolean") + _ = a.any(skipna=True) + _ = a.all(skipna=True) + _ = a.sum(skipna=True) + _ = a & b + _ = a | b + _ = ~a + _ = a.fillna(False) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "boolean_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_boolean_array.ts b/benchmarks/tsb/bench_boolean_array.ts new file mode 100644 index 00000000..9d00600e --- /dev/null +++ b/benchmarks/tsb/bench_boolean_array.ts @@ -0,0 +1,46 @@ +/** + * Benchmark: BooleanArray — nullable boolean extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/any/all/sum/and/or/not/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 5; +const ITERATIONS = 50; + +// Build input with ~10% nulls (same pattern across TS and Python) +const raw: (boolean | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : i % 3 !== 0, +); + +// Build a second array for bitwise ops +const raw2: (boolean | null)[] = Array.from({ length: N }, (_, i) => + i % 7 === 0 ? null : i % 2 === 0, +); + +function run(): void { + const a = arrays.BooleanArray.from(raw); + const b = arrays.BooleanArray.from(raw2); + a.any(); + a.all(); + a.sum(); + a.and(b); + a.or(b); + a.not(); + a.fillna(false); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "boolean_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From e55e59067378eacb16dbafa78d0b4fefa2030f8b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 01:30:56 +0000 Subject: [PATCH 4/6] ci: trigger checks From 8d70dac506d156f176e6be57ef35c1b535f34623 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 13:28:16 +0000 Subject: [PATCH 5/6] [Autoloop: perf-comparison] Iteration 420: StringArray benchmark Run: https://github.com/githubnext/tsb/actions/runs/30096576504 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_string_array.py | 41 +++++++++++++++++++++++++ benchmarks/tsb/bench_string_array.ts | 40 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 benchmarks/pandas/bench_string_array.py create mode 100644 benchmarks/tsb/bench_string_array.ts diff --git a/benchmarks/pandas/bench_string_array.py b/benchmarks/pandas/bench_string_array.py new file mode 100644 index 00000000..fd9bcda2 --- /dev/null +++ b/benchmarks/pandas/bench_string_array.py @@ -0,0 +1,41 @@ +"""Benchmark: StringArray — nullable string extension array operations. +N=100_000 elements with ~10% nulls using pandas StringDtype. +Tests: from_sequence, upper, lower, strip, contains, len, fillna. +""" +import json +import time +import pandas as pd + +N = 100_000 +WARMUP = 3 +ITERATIONS = 50 + +WORDS = ["hello", "world", " foo ", "bar", "baz", " qux ", "quux", "corge", "grault", "garply"] + +raw = [(None if i % 10 == 0 else WORDS[i % len(WORDS)]) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="string") + _ = a.str.upper() + _ = a.str.lower() + _ = a.str.strip() + _ = a.str.contains("oo", na=False) + _ = a.str.len() + _ = a.fillna("NA") + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "string_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_string_array.ts b/benchmarks/tsb/bench_string_array.ts new file mode 100644 index 00000000..6902f524 --- /dev/null +++ b/benchmarks/tsb/bench_string_array.ts @@ -0,0 +1,40 @@ +/** + * Benchmark: StringArray — nullable string extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/upper/lower/strip/contains/len/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 50; + +const WORDS = ["hello", "world", " foo ", "bar", "baz", " qux ", "quux", "corge", "grault", "garply"]; + +const raw: (string | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : WORDS[i % WORDS.length], +); + +function run(): void { + const a = arrays.StringArray.from(raw); + a.upper(); + a.lower(); + a.strip(); + a.contains("oo"); + a.len(); + a.fillna("NA"); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "string_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 1b0a1f83240a24eb10a499aaa8a206b6a851ea92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 13:36:13 +0000 Subject: [PATCH 6/6] ci: trigger checks