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/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/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_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, + }), +); 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, + }), +); 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, + }), +);