Skip to content

Commit ad90a48

Browse files
committed
feat(python/timing): export benchmark to python
1 parent 7c65726 commit ad90a48

File tree

4 files changed

+112
-6
lines changed

4 files changed

+112
-6
lines changed

binding/timing_binding.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
*/
55

66
#include <chrono>
7-
#include <cstdint>
8-
#include <format>
7+
#include <cstddef>
98
#include <functional>
9+
#include <string>
1010

1111
#include <pybind11/pybind11.h>
1212

13+
#include "timing/benchmark.hpp"
1314
#include "timing/timer.hpp"
1415

1516
namespace py = pybind11;
@@ -22,10 +23,13 @@ auto GetElapsedWrapper(const Timer &timer) {
2223
return timer.GetElapsed<DurationType>();
2324
}
2425

25-
auto TimeFunctionWrapper(const std::function<void()> &func) -> std::int64_t {
26-
return TimeFunction(func);
26+
auto BenchmarkWrapper(const std::string &name, const std::function<void()> &func,
27+
std::size_t iterations = 1000) {
28+
return BenchmarkRunner::Benchmark(name, func, iterations);
2729
}
2830

31+
auto TimeFunctionWrapper(const std::function<void()> &func) { return TimeFunction(func); }
32+
2933
} // namespace
3034

3135
void BindTiming(py::module &m) {
@@ -41,6 +45,20 @@ void BindTiming(py::module &m) {
4145
.def("get_elapsed_s", &GetElapsedWrapper<std::chrono::seconds>)
4246
.def("get_elapsed_str", &Timer::GetElapsedString);
4347

48+
// Bind BenchmarkResult struct
49+
py::class_<BenchmarkRunner::BenchmarkResult>(m, "BenchmarkResult")
50+
.def_readwrite("name", &BenchmarkRunner::BenchmarkResult::name)
51+
.def_readwrite("iterations", &BenchmarkRunner::BenchmarkResult::iterations)
52+
.def_readwrite("total_ns", &BenchmarkRunner::BenchmarkResult::total_ns)
53+
.def_readwrite("avg_ns", &BenchmarkRunner::BenchmarkResult::avg_ns)
54+
.def_readwrite("min_ns", &BenchmarkRunner::BenchmarkResult::min_ns)
55+
.def_readwrite("max_ns", &BenchmarkRunner::BenchmarkResult::max_ns);
56+
57+
// Bind BenchmarkRunner class
58+
py::class_<BenchmarkRunner>(m, "BenchmarkRunner")
59+
.def_static("benchmark", &BenchmarkWrapper)
60+
.def_static("print_result", &BenchmarkRunner::PrintResult);
61+
4462
// Bind utility functions
4563
m.def("to_human_readable", &ToHumanReadable);
4664
m.def("time_function", &TimeFunctionWrapper);

python/src/demo/algorithms.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def pipeline(*functions: Callable[[Any], Any]) -> Callable[[Any], Any]:
154154
... lambda data: [x for x in data if x > 5],
155155
... sum,
156156
... )
157+
...
157158
>>> process([1, 2, 3, 4, 5])
158159
24
159160
"""

python/src/demo/containers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def __iter__(self) -> Iterator[T]:
143143
>>> container = Container(int, [1, 2, 3])
144144
>>> for item in container:
145145
... print(item)
146+
...
146147
1
147148
2
148149
3

python/src/demo/timing.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Python wrapper for the timing module."""
22

3-
import statistics
43
from contextlib import contextmanager
4+
from dataclasses import dataclass
55
from types import TracebackType
6-
from typing import Any, Callable, ContextManager, Generator
6+
from typing import Any, Callable, Generator
77

88
import cpp_features.timing as _timing
99

@@ -152,9 +152,11 @@ def measure_time(name: str | None = None) -> Generator[Timer, None, None]:
152152
153153
Examples
154154
--------
155+
>>> from time import sleep
155156
>>> with measure_time('Scoped operation') as timer:
156157
... sleep(3)
157158
... print('Doing some work inside scoped timer...')
159+
...
158160
Scoped operation: 3s
159161
"""
160162
timer = Timer()
@@ -206,9 +208,93 @@ def time_function(func: Callable[[], Any]) -> int:
206208
return _timing.time_function(func)
207209

208210

211+
@dataclass
212+
class BenchmarkResult:
213+
"""Structure containing benchmark results and statistics."""
214+
215+
name: str
216+
iterations: int
217+
total_ns: int
218+
avg_ns: int
219+
min_ns: int
220+
max_ns: int
221+
222+
@staticmethod
223+
def from_cpp(result: _timing.BenchmarkResult) -> 'BenchmarkResult':
224+
"""Create a BenchmarkResult from the C++ BenchmarkResult.
225+
226+
Parameters
227+
----------
228+
result : _timing.BenchmarkResult
229+
The C++ BenchmarkResult to convert
230+
231+
Returns
232+
-------
233+
BenchmarkResult
234+
The converted BenchmarkResult
235+
"""
236+
return BenchmarkResult(
237+
name=result.name,
238+
iterations=result.iterations,
239+
total_ns=result.total_ns,
240+
avg_ns=result.avg_ns,
241+
min_ns=result.min_ns,
242+
max_ns=result.max_ns,
243+
)
244+
245+
def print(self) -> None:
246+
"""Print formatted benchmark results.
247+
248+
Prints comprehensive benchmark statistics in a human-readable format with appropriate units
249+
and formatting.
250+
"""
251+
_timing.BenchmarkRunner.print_result(self)
252+
253+
254+
def benchmark(
255+
name: str, func: Callable[[], Any], iterations: int = 1000
256+
) -> BenchmarkResult:
257+
"""Utility function to benchmark a function with a given number of iterations.
258+
259+
Parameters
260+
----------
261+
name : str
262+
Descriptive name for the benchmark
263+
func : Callable[[], Any]
264+
Function to benchmark
265+
iterations : int, default=1000
266+
Number of times to execute the function
267+
268+
Returns
269+
-------
270+
BenchmarkResult
271+
Structure containing timing statistics
272+
273+
Examples
274+
--------
275+
>>> from random import randint
276+
>>> def sort_data() -> None:
277+
... data = [randint(1, 100) for _ in range(1000)]
278+
... data.sort()
279+
...
280+
>>> result = benchmark('Sorting algorithm', sort_data, 1000)
281+
>>> result.print()
282+
Benchmark: Sorting algorithm
283+
- Iterations: 1000
284+
- Total time: 12.3ms
285+
- Average: 12.3μs
286+
- Min: 10.1μs
287+
- Max: 15.7μs
288+
"""
289+
result = _timing.BenchmarkRunner.benchmark(name, func, iterations)
290+
return BenchmarkResult.from_cpp(result)
291+
292+
209293
__all__ = [
210294
'Timer',
211295
'measure_time',
212296
'to_human_readable',
213297
'time_function',
298+
'BenchmarkResult',
299+
'benchmark',
214300
]

0 commit comments

Comments
 (0)