Skip to content

Commit 21ba246

Browse files
committed
feat: Optimize combine_reducers with Cython implementation, add Python fallback, and include benchmarks.
1 parent 915d622 commit 21ba246

File tree

7 files changed

+13586
-144
lines changed

7 files changed

+13586
-144
lines changed

CYTHON_README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ In Phase 9, we fully Cythonized the `Autorun` class, embedding it directly withi
4242

4343
This provides substantial improvements for applications that rely heavily on reactive state derived from the store.
4444

45+
## Combine Reducers Optimization (Phase 10)
46+
47+
In Phase 10, we optimized `combine_reducers` by moving the dispatch loop and state aggregation logic to Cython.
48+
49+
| Test Case | Baseline (Python) | Optimized (Cython) | Speedup |
50+
|-----------|-------------------|--------------------|-------------|
51+
| **10 Reducers** | 12.9 μs | 10.5 μs | **1.23x** |
52+
| **50 Reducers** | 69.5 μs | 55.3 μs | **1.26x** |
53+
| **100 Reducers** | 178.0 μs | 154.5 μs | **1.15x** |
54+
55+
The dispatch loop avoids Python attribute access overhead (`getattr`) during state decomposition and aggregates results efficiently, though gains are capped by the execution time of the underlying Python reducers.
56+
4557
## Build & Reproduction
4658

4759
To reproduce these results, you can build the extension and run the benchmarks.
@@ -79,3 +91,4 @@ pytest-benchmark compare baseline.json optimized.json
7991
- `redux/main.py`: The selector module that handles the import logic.
8092
- `benchmarks/bench_dispatch.py`: The performance test suite for dispatch.
8193
- `benchmarks/bench_autorun.py`: The performance test suite for Autorun.
94+
- `benchmarks/bench_combine_reducers.py`: The performance test suite for combine_reducers.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
from __future__ import annotations
3+
import uuid
4+
from typing import Any
5+
import pytest
6+
from redux import (
7+
BaseAction,
8+
BaseEvent,
9+
CompleteReducerResult,
10+
InitAction,
11+
combine_reducers,
12+
)
13+
from redux.basic_types import Immutable
14+
15+
class State(Immutable):
16+
value: int
17+
18+
class IncrementAction(BaseAction):
19+
pass
20+
21+
def counter_reducer(state: State | None, action: Any) -> State:
22+
if state is None:
23+
return State(value=0)
24+
if isinstance(action, IncrementAction):
25+
return State(value=state.value + 1)
26+
return state
27+
28+
def create_reducers(count: int):
29+
return {f'r{i}': counter_reducer for i in range(count)}
30+
31+
@pytest.mark.benchmark(group='combine_reducers_creation')
32+
def test_creation(benchmark):
33+
reducers = create_reducers(10)
34+
35+
def run():
36+
combine_reducers(State, **reducers)
37+
38+
benchmark(run)
39+
40+
@pytest.mark.benchmark(group='combine_reducers_dispatch')
41+
def test_dispatch_10_reducers(benchmark):
42+
reducers = create_reducers(10)
43+
reducer, _ = combine_reducers(State, **reducers)
44+
state = reducer(None, InitAction()).state
45+
action = IncrementAction()
46+
47+
def run():
48+
reducer(state, action)
49+
50+
benchmark(run)
51+
52+
@pytest.mark.benchmark(group='combine_reducers_dispatch')
53+
def test_dispatch_50_reducers(benchmark):
54+
reducers = create_reducers(50)
55+
reducer, _ = combine_reducers(State, **reducers)
56+
state = reducer(None, InitAction()).state
57+
action = IncrementAction()
58+
59+
def run():
60+
reducer(state, action)
61+
62+
benchmark(run)
63+
64+
@pytest.mark.benchmark(group='combine_reducers_dispatch')
65+
def test_dispatch_100_reducers(benchmark):
66+
reducers = create_reducers(100)
67+
reducer, _ = combine_reducers(State, **reducers)
68+
state = reducer(None, InitAction()).state
69+
action = IncrementAction()
70+
71+
def run():
72+
reducer(state, action)
73+
74+
benchmark(run)

0 commit comments

Comments
 (0)