Skip to content

Commit 1df361d

Browse files
Iteration 166: 5 new benchmark pairs (534 total, +5 vs best 529)
Adds benchmarks for: str_split_method, categorical_index_modify, applySeries_fn, dataframe_apply_stats, dataframe_from_columns. Cherry-picked iters 159-165 from diverged branch (+21) plus these 5 brings canonical branch from 508 to 534 pairs. Run: https://github.com/githubnext/tsessebe/actions/runs/24587057857 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a770db3 commit 1df361d

10 files changed

Lines changed: 389 additions & 0 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Benchmark: pandas Series.apply() with (value) lambda — 100k-element Series.
3+
Mirrors tsb's applySeries (stats/apply.ts) behavior.
4+
Outputs JSON: {"function": "applySeries_fn", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import pandas as pd
9+
10+
SIZE = 100_000
11+
WARMUP = 5
12+
ITERATIONS = 30
13+
14+
s = pd.Series([i * 0.5 for i in range(SIZE)])
15+
16+
fn = lambda v: v * 2 + 1 # noqa: E731
17+
18+
for _ in range(WARMUP):
19+
s.apply(fn)
20+
21+
times = []
22+
for _ in range(ITERATIONS):
23+
t0 = time.perf_counter()
24+
s.apply(fn)
25+
times.append((time.perf_counter() - t0) * 1000)
26+
27+
total_ms = sum(times)
28+
mean_ms = total_ms / ITERATIONS
29+
print(json.dumps({
30+
"function": "applySeries_fn",
31+
"mean_ms": mean_ms,
32+
"iterations": ITERATIONS,
33+
"total_ms": total_ms,
34+
}))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Benchmark: pandas CategoricalIndex modification — rename_categories, reorder_categories,
3+
remove_categories, set_categories, remove_unused_categories on a 10k-element index.
4+
Outputs JSON: {"function": "categorical_index_modify", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import pandas as pd
9+
10+
SIZE = 10_000
11+
WARMUP = 5
12+
ITERATIONS = 50
13+
14+
CATS = ["alpha", "beta", "gamma", "delta", "epsilon"]
15+
labels = [CATS[i % len(CATS)] for i in range(SIZE)]
16+
ci = pd.CategoricalIndex(labels)
17+
18+
for _ in range(WARMUP):
19+
ci.rename_categories(["A", "B", "C", "D", "E"])
20+
ci.reorder_categories(["epsilon", "delta", "gamma", "beta", "alpha"])
21+
ci.remove_categories(["epsilon"])
22+
ci.set_categories(["alpha", "beta", "gamma"])
23+
ci.remove_unused_categories()
24+
ci.as_ordered()
25+
ci.as_unordered()
26+
27+
times = []
28+
for _ in range(ITERATIONS):
29+
t0 = time.perf_counter()
30+
ci.rename_categories(["A", "B", "C", "D", "E"])
31+
ci.reorder_categories(["epsilon", "delta", "gamma", "beta", "alpha"])
32+
ci.remove_categories(["epsilon"])
33+
ci.set_categories(["alpha", "beta", "gamma"])
34+
ci.remove_unused_categories()
35+
ci.as_ordered()
36+
ci.as_unordered()
37+
times.append((time.perf_counter() - t0) * 1000)
38+
39+
total_ms = sum(times)
40+
mean_ms = total_ms / ITERATIONS
41+
print(json.dumps({
42+
"function": "categorical_index_modify",
43+
"mean_ms": mean_ms,
44+
"iterations": ITERATIONS,
45+
"total_ms": total_ms,
46+
}))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Benchmark: pandas DataFrame.apply() — apply fn to each column (axis=0) and row (axis=1).
3+
Mirrors tsb's dataFrameApply (stats/apply.ts) behavior.
4+
Outputs JSON: {"function": "dataframe_apply_stats", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import pandas as pd
9+
import numpy as np
10+
11+
SIZE = 10_000
12+
WARMUP = 3
13+
ITERATIONS = 20
14+
15+
df = pd.DataFrame({
16+
"a": (np.arange(SIZE) * 1.0),
17+
"b": (np.arange(SIZE) * 2.0),
18+
"c": (np.arange(SIZE) * 3.0),
19+
})
20+
21+
sum_fn = lambda col: col.mean() # noqa: E731
22+
23+
for _ in range(WARMUP):
24+
df.apply(sum_fn, axis=0)
25+
df.apply(sum_fn, axis=1)
26+
27+
times = []
28+
for _ in range(ITERATIONS):
29+
t0 = time.perf_counter()
30+
df.apply(sum_fn, axis=0)
31+
df.apply(sum_fn, axis=1)
32+
times.append((time.perf_counter() - t0) * 1000)
33+
34+
total_ms = sum(times)
35+
mean_ms = total_ms / ITERATIONS
36+
print(json.dumps({
37+
"function": "dataframe_apply_stats",
38+
"mean_ms": mean_ms,
39+
"iterations": ITERATIONS,
40+
"total_ms": total_ms,
41+
}))
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Benchmark: pandas DataFrame() construction — create 100k-row DataFrame from column arrays.
3+
Mirrors tsb's DataFrame.fromColumns() behavior.
4+
Outputs JSON: {"function": "dataframe_from_columns", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import pandas as pd
9+
import numpy as np
10+
11+
SIZE = 100_000
12+
WARMUP = 5
13+
ITERATIONS = 30
14+
15+
col_a = np.arange(SIZE, dtype=float)
16+
col_b = np.arange(SIZE, dtype=float) * 2.5
17+
col_c = np.arange(SIZE) % 1000
18+
col_d = np.sin(np.arange(SIZE) * 0.001)
19+
20+
for _ in range(WARMUP):
21+
pd.DataFrame({"a": col_a, "b": col_b, "c": col_c, "d": col_d})
22+
23+
times = []
24+
for _ in range(ITERATIONS):
25+
t0 = time.perf_counter()
26+
pd.DataFrame({"a": col_a, "b": col_b, "c": col_c, "d": col_d})
27+
times.append((time.perf_counter() - t0) * 1000)
28+
29+
total_ms = sum(times)
30+
mean_ms = total_ms / ITERATIONS
31+
print(json.dumps({
32+
"function": "dataframe_from_columns",
33+
"mean_ms": mean_ms,
34+
"iterations": ITERATIONS,
35+
"total_ms": total_ms,
36+
}))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Benchmark: pandas Series.str.split() — split strings by delimiter on 100k strings.
3+
Outputs JSON: {"function": "str_split_method", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
SIZE = 100_000
10+
WARMUP = 5
11+
ITERATIONS = 30
12+
13+
data = [f"part{i % 100}_b{i % 50}_c{i % 25}" for i in range(SIZE)]
14+
s = pd.Series(data)
15+
16+
for _ in range(WARMUP):
17+
s.str.split("_")
18+
s.str.split("_", n=2)
19+
20+
times = []
21+
for _ in range(ITERATIONS):
22+
t0 = time.perf_counter()
23+
s.str.split("_")
24+
s.str.split("_", n=2)
25+
times.append((time.perf_counter() - t0) * 1000)
26+
27+
total_ms = sum(times)
28+
mean_ms = total_ms / ITERATIONS
29+
print(json.dumps({
30+
"function": "str_split_method",
31+
"mean_ms": mean_ms,
32+
"iterations": ITERATIONS,
33+
"total_ms": total_ms,
34+
}))
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Benchmark: applySeries (stats/apply.ts) — element-wise fn receiving (value, label) on 100k-element Series.
3+
* This is the standalone stats version, distinct from seriesApply (core/pipe_apply.ts).
4+
* Outputs JSON: {"function": "applySeries_fn", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
*/
6+
import { Series, applySeries } from "../../src/index.ts";
7+
import type { Scalar, Label } from "../../src/types.ts";
8+
9+
const SIZE = 100_000;
10+
const WARMUP = 5;
11+
const ITERATIONS = 30;
12+
13+
const s = new Series({ data: Array.from({ length: SIZE }, (_, i) => i * 0.5) });
14+
15+
const fn = (v: Scalar, _label: Label): Scalar => (v as number) * 2 + 1;
16+
17+
for (let i = 0; i < WARMUP; i++) {
18+
applySeries(s, fn);
19+
}
20+
21+
const times: number[] = [];
22+
for (let i = 0; i < ITERATIONS; i++) {
23+
const t0 = performance.now();
24+
applySeries(s, fn);
25+
times.push(performance.now() - t0);
26+
}
27+
28+
const total = times.reduce((a, b) => a + b, 0);
29+
console.log(
30+
JSON.stringify({
31+
function: "applySeries_fn",
32+
mean_ms: total / ITERATIONS,
33+
iterations: ITERATIONS,
34+
total_ms: total,
35+
}),
36+
);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Benchmark: CategoricalIndex modification — renameCategories, reorderCategories, removeCategories,
3+
* setCategories, removeUnusedCategories, asOrdered/asUnordered on a 10k-element index.
4+
* Outputs JSON: {"function": "categorical_index_modify", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
*/
6+
import { CategoricalIndex } from "../../src/index.ts";
7+
8+
const SIZE = 10_000;
9+
const WARMUP = 5;
10+
const ITERATIONS = 50;
11+
12+
const CATS = ["alpha", "beta", "gamma", "delta", "epsilon"];
13+
const labels = Array.from({ length: SIZE }, (_, i) => CATS[i % CATS.length]);
14+
const ci = CategoricalIndex.fromArray(labels);
15+
16+
for (let i = 0; i < WARMUP; i++) {
17+
ci.renameCategories(["A", "B", "C", "D", "E"]);
18+
ci.reorderCategories(["epsilon", "delta", "gamma", "beta", "alpha"]);
19+
ci.removeCategories(["epsilon"]);
20+
ci.setCategories(["alpha", "beta", "gamma"]);
21+
ci.removeUnusedCategories();
22+
ci.asOrdered();
23+
ci.asUnordered();
24+
}
25+
26+
const times: number[] = [];
27+
for (let i = 0; i < ITERATIONS; i++) {
28+
const t0 = performance.now();
29+
ci.renameCategories(["A", "B", "C", "D", "E"]);
30+
ci.reorderCategories(["epsilon", "delta", "gamma", "beta", "alpha"]);
31+
ci.removeCategories(["epsilon"]);
32+
ci.setCategories(["alpha", "beta", "gamma"]);
33+
ci.removeUnusedCategories();
34+
ci.asOrdered();
35+
ci.asUnordered();
36+
times.push(performance.now() - t0);
37+
}
38+
39+
const total = times.reduce((a, b) => a + b, 0);
40+
console.log(
41+
JSON.stringify({
42+
function: "categorical_index_modify",
43+
mean_ms: total / ITERATIONS,
44+
iterations: ITERATIONS,
45+
total_ms: total,
46+
}),
47+
);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Benchmark: dataFrameApply (stats/apply.ts) — apply fn to each column (axis=0) and each row (axis=1)
3+
* on a 10k-row DataFrame. This is the standalone stats function, not df.apply().
4+
* Outputs JSON: {"function": "dataframe_apply_stats", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
*/
6+
import { DataFrame, Series, dataFrameApply } from "../../src/index.ts";
7+
import type { Scalar } from "../../src/types.ts";
8+
9+
const SIZE = 10_000;
10+
const WARMUP = 3;
11+
const ITERATIONS = 20;
12+
13+
const df = DataFrame.fromColumns({
14+
a: Array.from({ length: SIZE }, (_, i) => i * 1.0),
15+
b: Array.from({ length: SIZE }, (_, i) => i * 2.0),
16+
c: Array.from({ length: SIZE }, (_, i) => i * 3.0),
17+
});
18+
19+
const sumFn = (slice: Series<Scalar>) =>
20+
slice.values.reduce((acc, v) => acc + (v as number), 0) / slice.length;
21+
22+
for (let i = 0; i < WARMUP; i++) {
23+
dataFrameApply(df, sumFn, { axis: 0 });
24+
dataFrameApply(df, sumFn, { axis: 1 });
25+
}
26+
27+
const times: number[] = [];
28+
for (let i = 0; i < ITERATIONS; i++) {
29+
const t0 = performance.now();
30+
dataFrameApply(df, sumFn, { axis: 0 });
31+
dataFrameApply(df, sumFn, { axis: 1 });
32+
times.push(performance.now() - t0);
33+
}
34+
35+
const total = times.reduce((a, b) => a + b, 0);
36+
console.log(
37+
JSON.stringify({
38+
function: "dataframe_apply_stats",
39+
mean_ms: total / ITERATIONS,
40+
iterations: ITERATIONS,
41+
total_ms: total,
42+
}),
43+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Benchmark: DataFrame.fromColumns() — construct a 100k-row DataFrame from column arrays.
3+
* Tests the performance of the most common DataFrame construction path.
4+
* Outputs JSON: {"function": "dataframe_from_columns", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
*/
6+
import { DataFrame } from "../../src/index.ts";
7+
8+
const SIZE = 100_000;
9+
const WARMUP = 5;
10+
const ITERATIONS = 30;
11+
12+
const colA = Array.from({ length: SIZE }, (_, i) => i * 1.0);
13+
const colB = Array.from({ length: SIZE }, (_, i) => i * 2.5);
14+
const colC = Array.from({ length: SIZE }, (_, i) => i % 1000);
15+
const colD = Array.from({ length: SIZE }, (_, i) => Math.sin(i * 0.001));
16+
17+
for (let i = 0; i < WARMUP; i++) {
18+
DataFrame.fromColumns({ a: colA, b: colB, c: colC, d: colD });
19+
}
20+
21+
const times: number[] = [];
22+
for (let i = 0; i < ITERATIONS; i++) {
23+
const t0 = performance.now();
24+
DataFrame.fromColumns({ a: colA, b: colB, c: colC, d: colD });
25+
times.push(performance.now() - t0);
26+
}
27+
28+
const total = times.reduce((a, b) => a + b, 0);
29+
console.log(
30+
JSON.stringify({
31+
function: "dataframe_from_columns",
32+
mean_ms: total / ITERATIONS,
33+
iterations: ITERATIONS,
34+
total_ms: total,
35+
}),
36+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Benchmark: StringAccessor.split() — s.str.split(pat, n) on 100k strings.
3+
* Distinct from strSplitExpand (which uses the standalone function).
4+
* Outputs JSON: {"function": "str_split_method", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
*/
6+
import { Series } from "../../src/index.ts";
7+
8+
const SIZE = 100_000;
9+
const WARMUP = 5;
10+
const ITERATIONS = 30;
11+
12+
const data = Array.from({ length: SIZE }, (_, i) => `part${i % 100}_b${i % 50}_c${i % 25}`);
13+
const s = new Series({ data });
14+
15+
for (let i = 0; i < WARMUP; i++) {
16+
s.str.split("_");
17+
s.str.split("_", undefined, 2);
18+
}
19+
20+
const times: number[] = [];
21+
for (let i = 0; i < ITERATIONS; i++) {
22+
const t0 = performance.now();
23+
s.str.split("_");
24+
s.str.split("_", undefined, 2);
25+
times.push(performance.now() - t0);
26+
}
27+
28+
const total = times.reduce((a, b) => a + b, 0);
29+
console.log(
30+
JSON.stringify({
31+
function: "str_split_method",
32+
mean_ms: total / ITERATIONS,
33+
iterations: ITERATIONS,
34+
total_ms: total,
35+
}),
36+
);

0 commit comments

Comments
 (0)