Skip to content

Commit 081cb30

Browse files
Iteration 142: Add 9 benchmark pairs (429 total, +9 vs 420)
Added 9 new benchmark pairs: - groupby_multi_key: DataFrameGroupBy with multi-column keys ["dept","region"] vs pandas multi-key groupby - timestamp_static: Timestamp.fromComponents/fromisoformat/fromtimestamp vs pd.Timestamp static ctors - tz_datetime_index_ops: TZDatetimeIndex.toLocalStrings/sort/unique/filter/contains vs tz-aware DatetimeIndex ops - rolling_center_min_periods: Rolling with center=true and minPeriods options vs pandas rolling center/min_periods - cast_scalar: castScalar type coercion vs Python int()/float()/str() conversions - concat_options: concat with join="inner" and ignoreIndex=true vs pd.concat join/ignore_index - ewm_com_halflife: EWM with com and halflife params vs pandas ewm(com/halflife) - nat_sort_key: natSortKey tokenizer vs Python regex-based natural sort key - dataframe_iter: DataFrame.items()/iterrows() vs pandas df.items()/iterrows() Note: State file claimed best was 428 (from iters 140/141 that were not pushed to branch); actual branch had 420 pairs. This iteration rebuilds to 429 (new actual best). Run: https://github.com/githubnext/tsessebe/actions/runs/24545567127 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent bae766a commit 081cb30

18 files changed

Lines changed: 742 additions & 0 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Benchmark: Python type coercion equivalents — int(), float(), str(), bool() conversions.
3+
Outputs JSON: {"function": "cast_scalar", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
8+
SIZE = 100_000
9+
WARMUP = 5
10+
ITERATIONS = 50
11+
12+
int_values = [i % 1000 for i in range(SIZE)]
13+
float_values = [i * 0.5 for i in range(SIZE)]
14+
str_values = [str(i % 1000) for i in range(SIZE)]
15+
bool_values = [i % 2 == 0 for i in range(SIZE)]
16+
17+
for _ in range(WARMUP):
18+
for j in range(SIZE):
19+
int(float_values[j])
20+
float(int_values[j])
21+
int(str_values[j])
22+
int(bool_values[j])
23+
24+
times = []
25+
for _ in range(ITERATIONS):
26+
t0 = time.perf_counter()
27+
for j in range(SIZE):
28+
int(float_values[j])
29+
float(int_values[j])
30+
int(str_values[j])
31+
int(bool_values[j])
32+
times.append((time.perf_counter() - t0) * 1000)
33+
34+
total_ms = sum(times)
35+
mean_ms = total_ms / ITERATIONS
36+
print(json.dumps({"function": "cast_scalar", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
Benchmark: pandas concat with join="inner" and ignore_index=True options.
3+
Outputs JSON: {"function": "concat_options", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
ROWS = 50_000
10+
WARMUP = 5
11+
ITERATIONS = 20
12+
13+
df1 = pd.DataFrame({
14+
"a": [i * 1.0 for i in range(ROWS)],
15+
"b": [i * 2.0 for i in range(ROWS)],
16+
"c": [i * 3.0 for i in range(ROWS)],
17+
})
18+
df2 = pd.DataFrame({
19+
"a": [i * 1.5 for i in range(ROWS)],
20+
"b": [i * 2.5 for i in range(ROWS)],
21+
"d": [i * 4.0 for i in range(ROWS)],
22+
})
23+
24+
for _ in range(WARMUP):
25+
pd.concat([df1, df2], join="inner", ignore_index=True)
26+
pd.concat([df1, df2], join="outer", ignore_index=True)
27+
28+
times = []
29+
for _ in range(ITERATIONS):
30+
t0 = time.perf_counter()
31+
pd.concat([df1, df2], join="inner", ignore_index=True)
32+
pd.concat([df1, df2], join="outer", ignore_index=True)
33+
times.append((time.perf_counter() - t0) * 1000)
34+
35+
total_ms = sum(times)
36+
mean_ms = total_ms / ITERATIONS
37+
print(json.dumps({"function": "concat_options", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Benchmark: pandas DataFrame.items() / DataFrame.iterrows() — column and row iteration.
3+
Outputs JSON: {"function": "dataframe_iter", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
ROWS = 10_000
10+
WARMUP = 5
11+
ITERATIONS = 50
12+
13+
df = pd.DataFrame({
14+
"a": [i * 1.0 for i in range(ROWS)],
15+
"b": [i * 2.0 for i in range(ROWS)],
16+
"c": [i * 3.0 for i in range(ROWS)],
17+
})
18+
19+
20+
def consume_items(df: pd.DataFrame) -> None:
21+
for _, s in df.items():
22+
_ = s.sum()
23+
24+
25+
def consume_iterrows(df: pd.DataFrame) -> None:
26+
count = 0
27+
for _ in df.iterrows():
28+
count += 1
29+
30+
31+
for _ in range(WARMUP):
32+
consume_items(df)
33+
consume_iterrows(df)
34+
35+
times = []
36+
for _ in range(ITERATIONS):
37+
t0 = time.perf_counter()
38+
consume_items(df)
39+
consume_iterrows(df)
40+
times.append((time.perf_counter() - t0) * 1000)
41+
42+
total_ms = sum(times)
43+
mean_ms = total_ms / ITERATIONS
44+
print(json.dumps({"function": "dataframe_iter", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Benchmark: pandas EWM with com and halflife decay parameters.
3+
Outputs JSON: {"function": "ewm_com_halflife", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
import numpy as np
9+
10+
ROWS = 100_000
11+
WARMUP = 3
12+
ITERATIONS = 10
13+
14+
data = [float(np.sin(i * 0.05)) for i in range(ROWS)]
15+
s = pd.Series(data)
16+
17+
for _ in range(WARMUP):
18+
s.ewm(com=9).mean()
19+
s.ewm(halflife=10).mean()
20+
s.ewm(com=5).std()
21+
s.ewm(halflife=7).var()
22+
23+
times = []
24+
for _ in range(ITERATIONS):
25+
t0 = time.perf_counter()
26+
s.ewm(com=9).mean()
27+
s.ewm(halflife=10).mean()
28+
s.ewm(com=5).std()
29+
s.ewm(halflife=7).var()
30+
times.append((time.perf_counter() - t0) * 1000)
31+
32+
total_ms = sum(times)
33+
mean_ms = total_ms / ITERATIONS
34+
print(json.dumps({"function": "ewm_com_halflife", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Benchmark: pandas DataFrame groupby with multiple key columns.
3+
Outputs JSON: {"function": "groupby_multi_key", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
ROWS = 100_000
10+
WARMUP = 3
11+
ITERATIONS = 10
12+
13+
depts = ["eng", "sales", "hr", "ops"]
14+
regions = ["north", "south", "east", "west"]
15+
dept = [depts[i % len(depts)] for i in range(ROWS)]
16+
region = [regions[i % len(regions)] for i in range(ROWS)]
17+
value = [i * 0.5 for i in range(ROWS)]
18+
bonus = [i * 0.1 for i in range(ROWS)]
19+
20+
df = pd.DataFrame({"dept": dept, "region": region, "value": value, "bonus": bonus})
21+
22+
for _ in range(WARMUP):
23+
df.groupby(["dept", "region"]).sum()
24+
df.groupby(["dept", "region"]).mean()
25+
26+
times = []
27+
for _ in range(ITERATIONS):
28+
t0 = time.perf_counter()
29+
df.groupby(["dept", "region"]).sum()
30+
df.groupby(["dept", "region"]).mean()
31+
times.append((time.perf_counter() - t0) * 1000)
32+
33+
total_ms = sum(times)
34+
mean_ms = total_ms / ITERATIONS
35+
print(json.dumps({"function": "groupby_multi_key", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Benchmark: Python natural sort key equivalent — natsort library or manual tokenization.
3+
Uses natsort if available, else falls back to a simple tokenizer.
4+
Outputs JSON: {"function": "nat_sort_key", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import re
9+
10+
SIZE = 10_000
11+
WARMUP = 5
12+
ITERATIONS = 50
13+
14+
data = [f"file{i % 1000}_v{(i % 10) + 1}.{i % 100}" for i in range(SIZE)]
15+
mixed_case = [f"Item{i % 500}_Part{(i % 20) + 1}" for i in range(SIZE)]
16+
17+
18+
def nat_sort_key(s: str, ignore_case: bool = False) -> list:
19+
"""Simple natural sort key tokenizer (matches tsb natSortKey logic)."""
20+
if ignore_case:
21+
s = s.lower()
22+
parts = re.split(r"(\d+)", s)
23+
return [int(p) if p.isdigit() else p for p in parts]
24+
25+
26+
for _ in range(WARMUP):
27+
for j in range(SIZE):
28+
nat_sort_key(data[j])
29+
nat_sort_key(mixed_case[j], ignore_case=True)
30+
31+
times = []
32+
for _ in range(ITERATIONS):
33+
t0 = time.perf_counter()
34+
for j in range(SIZE):
35+
nat_sort_key(data[j])
36+
nat_sort_key(mixed_case[j], ignore_case=True)
37+
times.append((time.perf_counter() - t0) * 1000)
38+
39+
total_ms = sum(times)
40+
mean_ms = total_ms / ITERATIONS
41+
print(json.dumps({"function": "nat_sort_key", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Benchmark: pandas Rolling with center=True and min_periods options.
3+
Outputs JSON: {"function": "rolling_center_min_periods", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
import numpy as np
9+
10+
ROWS = 100_000
11+
WARMUP = 3
12+
ITERATIONS = 10
13+
14+
data = [float('nan') if i % 10 == 0 else float(np.sin(i * 0.01)) for i in range(ROWS)]
15+
s = pd.Series(data)
16+
17+
for _ in range(WARMUP):
18+
s.rolling(50, center=True).mean()
19+
s.rolling(100, min_periods=10).sum()
20+
s.rolling(30, center=True, min_periods=5).std()
21+
22+
times = []
23+
for _ in range(ITERATIONS):
24+
t0 = time.perf_counter()
25+
s.rolling(50, center=True).mean()
26+
s.rolling(100, min_periods=10).sum()
27+
s.rolling(30, center=True, min_periods=5).std()
28+
times.append((time.perf_counter() - t0) * 1000)
29+
30+
total_ms = sum(times)
31+
mean_ms = total_ms / ITERATIONS
32+
print(json.dumps({"function": "rolling_center_min_periods", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Benchmark: pandas Timestamp static constructors — fromtimestamp, fromisoformat, components.
3+
Outputs JSON: {"function": "timestamp_static", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
SIZE = 10_000
10+
WARMUP = 5
11+
ITERATIONS = 50
12+
13+
import datetime
14+
15+
iso_strings = [
16+
(datetime.datetime(2020, 1, 1) + datetime.timedelta(days=i)).isoformat()
17+
for i in range(SIZE)
18+
]
19+
timestamps_s = [
20+
(datetime.datetime(2020, 1, 1) + datetime.timedelta(hours=i)).timestamp()
21+
for i in range(SIZE)
22+
]
23+
24+
for _ in range(WARMUP):
25+
for j in range(SIZE):
26+
pd.Timestamp(year=2020, month=(j % 12) + 1, day=(j % 28) + 1)
27+
pd.Timestamp(iso_strings[j % len(iso_strings)])
28+
pd.Timestamp.fromtimestamp(timestamps_s[j % len(timestamps_s)])
29+
30+
times = []
31+
for _ in range(ITERATIONS):
32+
t0 = time.perf_counter()
33+
for j in range(SIZE):
34+
pd.Timestamp(year=2020, month=(j % 12) + 1, day=(j % 28) + 1)
35+
pd.Timestamp(iso_strings[j % len(iso_strings)])
36+
pd.Timestamp.fromtimestamp(timestamps_s[j % len(timestamps_s)])
37+
times.append((time.perf_counter() - t0) * 1000)
38+
39+
total_ms = sum(times)
40+
mean_ms = total_ms / ITERATIONS
41+
print(json.dumps({"function": "timestamp_static", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Benchmark: pandas DatetimeTZDtype index methods — tz_localize, sort_values, unique, filter, isin.
3+
Outputs JSON: {"function": "tz_datetime_index_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
SIZE = 10_000
10+
WARMUP = 5
11+
ITERATIONS = 50
12+
13+
naive = pd.date_range(start="2024-01-01", periods=SIZE, freq="h")
14+
tz_idx = naive.tz_localize("America/New_York")
15+
ref_date = pd.Timestamp("2024-06-01", tz="America/New_York")
16+
17+
for _ in range(WARMUP):
18+
tz_idx.strftime("%Y-%m-%d %H:%M:%S %Z")
19+
tz_idx.sort_values()
20+
tz_idx.unique()
21+
tz_idx[tz_idx >= ref_date]
22+
ref_date in tz_idx
23+
24+
times = []
25+
for _ in range(ITERATIONS):
26+
t0 = time.perf_counter()
27+
tz_idx.strftime("%Y-%m-%d %H:%M:%S %Z")
28+
tz_idx.sort_values()
29+
tz_idx.unique()
30+
tz_idx[tz_idx >= ref_date]
31+
ref_date in tz_idx
32+
times.append((time.perf_counter() - t0) * 1000)
33+
34+
total_ms = sum(times)
35+
mean_ms = total_ms / ITERATIONS
36+
print(json.dumps({"function": "tz_datetime_index_ops", "mean_ms": round(mean_ms, 3), "iterations": ITERATIONS, "total_ms": round(total_ms, 3)}))
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Benchmark: castScalar — type coercion of scalar values to various Dtype kinds.
3+
* Outputs JSON: {"function": "cast_scalar", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { castScalar, Dtype } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 50;
10+
11+
const intDtype = Dtype.from("int64");
12+
const floatDtype = Dtype.from("float64");
13+
const strDtype = Dtype.from("str");
14+
const boolDtype = Dtype.from("bool");
15+
16+
const intValues = Array.from({ length: SIZE }, (_, i) => i % 1000);
17+
const floatValues = Array.from({ length: SIZE }, (_, i) => i * 0.5);
18+
const strValues = Array.from({ length: SIZE }, (_, i) => String(i % 1000));
19+
const boolValues = Array.from({ length: SIZE }, (_, i) => i % 2 === 0);
20+
21+
for (let i = 0; i < WARMUP; i++) {
22+
for (let j = 0; j < SIZE; j++) {
23+
castScalar(floatValues[j], intDtype);
24+
castScalar(intValues[j], floatDtype);
25+
castScalar(strValues[j], intDtype);
26+
castScalar(boolValues[j], intDtype);
27+
}
28+
}
29+
30+
const times: number[] = [];
31+
for (let i = 0; i < ITERATIONS; i++) {
32+
const t0 = performance.now();
33+
for (let j = 0; j < SIZE; j++) {
34+
castScalar(floatValues[j], intDtype);
35+
castScalar(intValues[j], floatDtype);
36+
castScalar(strValues[j], intDtype);
37+
castScalar(boolValues[j], intDtype);
38+
}
39+
times.push(performance.now() - t0);
40+
}
41+
42+
const totalMs = times.reduce((a, b) => a + b, 0);
43+
const meanMs = totalMs / ITERATIONS;
44+
console.log(
45+
JSON.stringify({
46+
function: "cast_scalar",
47+
mean_ms: Math.round(meanMs * 1000) / 1000,
48+
iterations: ITERATIONS,
49+
total_ms: Math.round(totalMs * 1000) / 1000,
50+
}),
51+
);

0 commit comments

Comments
 (0)