Skip to content
Draft
79 changes: 79 additions & 0 deletions benchmarks/pandas/bench_register_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Benchmark: register_option — register custom options with pandas' options system.

Mirrors tsb registerOption which wraps pandas' core config register_option API.
Uses pandas.core.config_init / _config._registered_options to register custom
options with defaults and validators.

Outputs JSON: {"function": "register_option", "mean_ms": ..., "iterations": ..., "total_ms": ...}
"""
import json
import time

import pandas as pd

WARMUP = 5
ITERATIONS = 1_000

key_counter = [0]


def register_and_exercise():
key = f"bench.custom_{key_counter[0]}"
key_counter[0] += 1
# pandas does not expose a public register_option in the top-level namespace,
# but it is accessible via pd.core.config.register_option (internal API).
# We simulate the equivalent pattern: register → get → set → reset.
try:
pd.core.config.register_option(key, 42, "A custom numeric option for benchmarking.")
except Exception:
pass # already registered or unavailable
try:
v = pd.get_option(key)
pd.set_option(key, 99)
pd.reset_option(key)
_ = v
except Exception:
pass


def register_with_validator():
key = f"bench.validated_{key_counter[0]}"
key_counter[0] += 1

def validator(val):
if not isinstance(val, (int, float)) or val < 0:
raise ValueError("must be a non-negative number")

try:
pd.core.config.register_option(key, 10, "A validated option.", validator=validator)
except Exception:
pass
try:
pd.set_option(key, 50)
pd.reset_option(key)
except Exception:
pass


# Warm-up
for _ in range(WARMUP):
register_and_exercise()
register_with_validator()

start = time.perf_counter()
for _ in range(ITERATIONS):
register_and_exercise()
register_with_validator()
total_ms = (time.perf_counter() - start) * 1000

print(
json.dumps(
{
"function": "register_option",
"mean_ms": total_ms / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total_ms,
}
)
)
38 changes: 38 additions & 0 deletions benchmarks/pandas/bench_to_dict_series_orient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Benchmark: DataFrame.to_dict(orient="series") — converts each column to a pandas Series.

Mirrors tsb toDictOriented(df, "series").

Outputs JSON: {"function": "to_dict_series_orient", "mean_ms": ..., "iterations": ..., "total_ms": ...}
"""
import json
import time
import numpy as np
import pandas as pd

ROWS = 10_000
WARMUP = 5
ITERATIONS = 30

df = pd.DataFrame({
"id": np.arange(ROWS),
"value": np.arange(ROWS) * 1.5,
"label": [f"item_{i % 100}" for i in range(ROWS)],
"score": np.sin(np.arange(ROWS) * 0.01) * 100,
"flag": np.arange(ROWS) % 2 == 0,
})

for _ in range(WARMUP):
df.to_dict(orient="series")

t0 = time.perf_counter()
for _ in range(ITERATIONS):
df.to_dict(orient="series")
total = (time.perf_counter() - t0) * 1000

print(json.dumps({
"function": "to_dict_series_orient",
"mean_ms": total / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total,
}))
51 changes: 51 additions & 0 deletions benchmarks/pandas/bench_wasm_agg_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Benchmark: numpy aggregate operations — np.sum, np.mean, np.min, np.max, np.var, np.std, np.median
plus pandas rolling and expanding window ops on a 100k-element float64 array.

Mirrors tsb bench_wasm_agg_ops.ts.

Outputs JSON: {"function": "wasm_agg_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...}
"""
import json
import time
import numpy as np
import pandas as pd

SIZE = 100_000
WINDOW = 50
MIN_PERIODS = 1
WARMUP = 3
ITERATIONS = 20

data = np.sin(np.arange(SIZE) * 0.001) * 1000
series = pd.Series(data)


def run():
np.sum(data)
np.mean(data)
np.min(data)
np.max(data)
np.var(data, ddof=1)
np.std(data, ddof=1)
np.median(data)
series.rolling(window=WINDOW, min_periods=MIN_PERIODS).sum()
series.rolling(window=WINDOW, min_periods=MIN_PERIODS).mean()
series.expanding(min_periods=MIN_PERIODS).sum()
series.expanding(min_periods=MIN_PERIODS).mean()


for _ in range(WARMUP):
run()

start = time.perf_counter()
for _ in range(ITERATIONS):
run()
total = (time.perf_counter() - start) * 1000 # ms

print(json.dumps({
"function": "wasm_agg_ops",
"mean_ms": total / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total,
}))
64 changes: 64 additions & 0 deletions benchmarks/pandas/bench_wasm_rolling_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Benchmark: WASM rolling/expanding stats equivalents using pandas/numpy —
Series.rolling(50).min/max/var/std/median and Series.expanding().min/max/var/std/median
on a 100k-element float64 array.

Mirrors bench_wasm_rolling_stats.ts

Outputs JSON: {"function": "wasm_rolling_stats", "mean_ms": ..., "iterations": ..., "total_ms": ...}
"""

import json
import math
import time

import numpy as np
import pandas as pd

SIZE = 100_000
WINDOW = 50
MIN_PERIODS = 1
WARMUP = 3
ITERATIONS = 20

# Deterministic float64 data (same as TS counterpart)
data = np.array(
[math.sin(i * 0.001) * 100 + math.cos(i * 0.003) * 50 for i in range(SIZE)],
dtype=np.float64,
)
s = pd.Series(data)


def run_once() -> None:
s.rolling(WINDOW, min_periods=MIN_PERIODS).min()
s.rolling(WINDOW, min_periods=MIN_PERIODS).max()
s.rolling(WINDOW, min_periods=MIN_PERIODS).var()
s.rolling(WINDOW, min_periods=MIN_PERIODS).std()
s.rolling(WINDOW, min_periods=MIN_PERIODS).median()
s.expanding(min_periods=MIN_PERIODS).min()
s.expanding(min_periods=MIN_PERIODS).max()
s.expanding(min_periods=MIN_PERIODS).var()
s.expanding(min_periods=MIN_PERIODS).std()
s.expanding(min_periods=MIN_PERIODS).median()


# Warm-up
for _ in range(WARMUP):
run_once()

# Measured iterations
t0 = time.perf_counter()
for _ in range(ITERATIONS):
run_once()
total_ms = (time.perf_counter() - t0) * 1000

print(
json.dumps(
{
"function": "wasm_rolling_stats",
"mean_ms": total_ms / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total_ms,
}
)
)
61 changes: 61 additions & 0 deletions benchmarks/tsb/bench_register_option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Benchmark: registerOption — register custom options with the tsb options system.
*
* Mirrors pandas `pd.core.config.register_option` which allows users to
* register custom options with validators and defaults.
*
* Covers:
* - registerOption(key, default, doc) → register without validator
* - registerOption(key, default, doc, validator) → register with validator
* - getOption / setOption / resetOption on custom keys
*
* Outputs JSON: {"function": "register_option", "mean_ms": ..., "iterations": ..., "total_ms": ...}
*/
import { registerOption, getOption, setOption, resetOption } from "../../src/index.ts";

const WARMUP = 5;
const ITERATIONS = 1_000;

// Register options outside the loop (registration is one-time setup)
// Use unique keys per run to avoid conflicts with repeated registrations.
let keyCounter = 0;

function registerAndExercise(): void {
const key = `bench.custom_${keyCounter++}`;
registerOption(key, 42, "A custom numeric option for benchmarking.");
getOption(key);
setOption(key, 99);
resetOption(key);
}

function registerWithValidator(): void {
const key = `bench.validated_${keyCounter++}`;
registerOption(key, 10, "A validated numeric option.", (val) => {
if (typeof val !== "number" || val < 0) return "must be a non-negative number";
return undefined;
});
setOption(key, 50);
resetOption(key);
}

// Warm-up
for (let i = 0; i < WARMUP; i++) {
registerAndExercise();
registerWithValidator();
}

const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
registerAndExercise();
registerWithValidator();
}
const total_ms = performance.now() - start;

console.log(
JSON.stringify({
function: "register_option",
mean_ms: total_ms / ITERATIONS,
iterations: ITERATIONS,
total_ms: total_ms,
}),
);
41 changes: 41 additions & 0 deletions benchmarks/tsb/bench_to_dict_series_orient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Benchmark: toDictOriented with "series" orient — converts each DataFrame
* column to a Series, producing Record<string, Series<Scalar>>.
*
* Mirrors pandas DataFrame.to_dict(orient="series") which returns a dict of
* {column_name: Series} pairs.
*
* Outputs JSON: {"function": "to_dict_series_orient", "mean_ms": ..., "iterations": ..., "total_ms": ...}
*/
import { DataFrame, toDictOriented } from "../../src/index.js";

const ROWS = 10_000;
const WARMUP = 5;
const ITERATIONS = 30;

const df = DataFrame.fromColumns({
id: Array.from({ length: ROWS }, (_, i) => i),
value: Array.from({ length: ROWS }, (_, i) => i * 1.5),
label: Array.from({ length: ROWS }, (_, i) => `item_${i % 100}`),
score: Array.from({ length: ROWS }, (_, i) => Math.sin(i * 0.01) * 100),
flag: Array.from({ length: ROWS }, (_, i) => i % 2 === 0),
});

for (let i = 0; i < WARMUP; i++) {
toDictOriented(df, "series");
}

const t0 = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
toDictOriented(df, "series");
}
const total = performance.now() - t0;

console.log(
JSON.stringify({
function: "to_dict_series_orient",
mean_ms: total / ITERATIONS,
iterations: ITERATIONS,
total_ms: total,
}),
);
Loading
Loading