Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions benchmarks/pandas/bench_boolean_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
45 changes: 45 additions & 0 deletions benchmarks/pandas/bench_first_last_valid_index.py
Original file line number Diff line number Diff line change
@@ -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,
}))
41 changes: 41 additions & 0 deletions benchmarks/pandas/bench_string_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
46 changes: 46 additions & 0 deletions benchmarks/tsb/bench_boolean_array.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
);
52 changes: 52 additions & 0 deletions benchmarks/tsb/bench_first_last_valid_index.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
);
40 changes: 40 additions & 0 deletions benchmarks/tsb/bench_string_array.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
);
Loading