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
23 changes: 23 additions & 0 deletions benchmarks/pandas/bench_ewm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Benchmark: ewm (Exponentially Weighted Moving) aggregations on 100k-element pandas Series"""
import json, time, math
import numpy as np
import pandas as pd

ROWS = 100_000
WARMUP = 3
ITERATIONS = 10
data = [math.sin(i * 0.01) * 100 + 50 for i in range(ROWS)]
s = pd.Series(data)

for _ in range(WARMUP):
s.ewm(span=20).mean()
s.ewm(span=20).std()
s.ewm(span=20).var()

start = time.perf_counter()
for _ in range(ITERATIONS):
s.ewm(span=20).mean()
s.ewm(span=20).std()
s.ewm(span=20).var()
total = (time.perf_counter() - start) * 1000
print(json.dumps({"function": "ewm", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
20 changes: 20 additions & 0 deletions benchmarks/pandas/bench_multi_index_to_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Benchmark: MultiIndex.tolist() on 100k-pair MultiIndex"""
import json, time
import pandas as pd

ROWS = 100_000
WARMUP = 3
ITERATIONS = 10
a = [f"a{i % 100}" for i in range(ROWS)]
b = [i % 1000 for i in range(ROWS)]
tuples = list(zip(a, b))
mi = pd.MultiIndex.from_tuples(tuples)

for _ in range(WARMUP):
mi.tolist()

start = time.perf_counter()
for _ in range(ITERATIONS):
mi.tolist()
total = (time.perf_counter() - start) * 1000
print(json.dumps({"function": "multi_index_to_list", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
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,
}
)
)
47 changes: 47 additions & 0 deletions benchmarks/pandas/bench_string_array_str_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Benchmark: StringArray additional string operations —
lstrip, rstrip, startswith, endswith, replace, zfill
on a 100k-element nullable StringDtype array (~10 % nulls).

Mirrors pandas pd.array([...], dtype="string") str methods:
str.lstrip, str.rstrip, str.startswith, str.endswith, str.replace, str.zfill

Outputs JSON: {"function": "string_array_str_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...}
"""
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)]

a = pd.array(raw, dtype="string")


def run() -> None:
a.str.lstrip()
a.str.rstrip()
a.str.startswith(" he")
a.str.endswith("ld ")
a.str.replace("hello", "hi", regex=False)
a.str.zfill(12)


for _ in range(WARMUP):
run()

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

print(json.dumps({
"function": "string_array_str_ops",
"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,
}
)
)
34 changes: 34 additions & 0 deletions benchmarks/tsb/bench_ewm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Benchmark: EWM (Exponentially Weighted Moving) aggregations on 100k-element Series
*/
import { Series } from "../../src/index.js";

const ROWS = 100_000;
const WARMUP = 3;
const ITERATIONS = 10;
const data = Array.from({ length: ROWS }, (_, i) => Math.sin(i * 0.01) * 100 + 50);
const s = new Series({ data });

// Warm-up: ewm mean, std, var with span=20
for (let i = 0; i < WARMUP; i++) {
s.ewm({ span: 20 }).mean();
s.ewm({ span: 20 }).std();
s.ewm({ span: 20 }).var();
}

const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
s.ewm({ span: 20 }).mean();
s.ewm({ span: 20 }).std();
s.ewm({ span: 20 }).var();
}
const total = performance.now() - start;

console.log(
JSON.stringify({
function: "ewm",
mean_ms: total / ITERATIONS,
iterations: ITERATIONS,
total_ms: total,
}),
);
26 changes: 26 additions & 0 deletions benchmarks/tsb/bench_multi_index_to_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Benchmark: MultiIndex.toList() on 100k-pair MultiIndex
* Outputs JSON: {"function": "multi_index_to_list", "mean_ms": ..., "iterations": ..., "total_ms": ...}
*/
import { MultiIndex } from "../../src/index.js";

const ROWS = 100_000;
const WARMUP = 3;
const ITERATIONS = 10;
const a = Array.from({ length: ROWS }, (_, i) => `a${i % 100}`);
const b = Array.from({ length: ROWS }, (_, i) => i % 1000);
const tuples: [string, number][] = a.map((v, i) => [v, b[i] as number]);
const mi = new MultiIndex({ tuples });

for (let i = 0; i < WARMUP; i++) mi.toList();
const start = performance.now();
for (let i = 0; i < ITERATIONS; i++) mi.toList();
const total = performance.now() - start;
console.log(
JSON.stringify({
function: "multi_index_to_list",
mean_ms: total / ITERATIONS,
iterations: ITERATIONS,
total_ms: total,
}),
);
Loading
Loading