dsabench 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsabench-0.1.0/.gitignore +33 -0
- dsabench-0.1.0/CHANGELOG.md +44 -0
- dsabench-0.1.0/LICENSE +21 -0
- dsabench-0.1.0/PKG-INFO +337 -0
- dsabench-0.1.0/README.md +298 -0
- dsabench-0.1.0/examples/01_basic.py +17 -0
- dsabench-0.1.0/examples/02_decorator.py +25 -0
- dsabench-0.1.0/examples/03_auto.py +32 -0
- dsabench-0.1.0/examples/04_compare.py +44 -0
- dsabench-0.1.0/examples/05_complexity.py +33 -0
- dsabench-0.1.0/examples/06_export_and_graphs.py +37 -0
- dsabench-0.1.0/examples/07_async_generators_threads.py +45 -0
- dsabench-0.1.0/examples/demo.py +31 -0
- dsabench-0.1.0/pyproject.toml +93 -0
- dsabench-0.1.0/src/bench/__init__.py +86 -0
- dsabench-0.1.0/src/bench/auto.py +309 -0
- dsabench-0.1.0/src/bench/benchmark.py +423 -0
- dsabench-0.1.0/src/bench/compare.py +118 -0
- dsabench-0.1.0/src/bench/complexity.py +125 -0
- dsabench-0.1.0/src/bench/config.py +153 -0
- dsabench-0.1.0/src/bench/cpu.py +50 -0
- dsabench-0.1.0/src/bench/decorator.py +138 -0
- dsabench-0.1.0/src/bench/exceptions.py +30 -0
- dsabench-0.1.0/src/bench/export.py +224 -0
- dsabench-0.1.0/src/bench/graph.py +147 -0
- dsabench-0.1.0/src/bench/memory.py +113 -0
- dsabench-0.1.0/src/bench/profiler.py +155 -0
- dsabench-0.1.0/src/bench/py.typed +0 -0
- dsabench-0.1.0/src/bench/report.py +239 -0
- dsabench-0.1.0/src/bench/stats.py +116 -0
- dsabench-0.1.0/src/bench/timer.py +51 -0
- dsabench-0.1.0/src/bench/types.py +401 -0
- dsabench-0.1.0/src/bench/utils.py +172 -0
- dsabench-0.1.0/tests/conftest.py +17 -0
- dsabench-0.1.0/tests/test_async_gen.py +100 -0
- dsabench-0.1.0/tests/test_auto.py +123 -0
- dsabench-0.1.0/tests/test_bench.py +176 -0
- dsabench-0.1.0/tests/test_compare.py +100 -0
- dsabench-0.1.0/tests/test_complexity.py +82 -0
- dsabench-0.1.0/tests/test_config.py +95 -0
- dsabench-0.1.0/tests/test_cpu.py +34 -0
- dsabench-0.1.0/tests/test_decorator.py +119 -0
- dsabench-0.1.0/tests/test_export.py +107 -0
- dsabench-0.1.0/tests/test_graph.py +54 -0
- dsabench-0.1.0/tests/test_integration.py +58 -0
- dsabench-0.1.0/tests/test_memory.py +64 -0
- dsabench-0.1.0/tests/test_profiler.py +82 -0
- dsabench-0.1.0/tests/test_stats.py +68 -0
- dsabench-0.1.0/tests/test_timer.py +44 -0
- dsabench-0.1.0/tests/test_types.py +96 -0
- dsabench-0.1.0/tests/test_utils.py +100 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Byte-compiled / caches
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
|
|
8
|
+
# Packaging
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.eggs/
|
|
13
|
+
|
|
14
|
+
# Environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
|
|
19
|
+
# Coverage
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
coverage.xml
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# Editors / OS
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# Benchmark exports & plots (local runs)
|
|
31
|
+
*.bench.json
|
|
32
|
+
*.bench.csv
|
|
33
|
+
*.bench.md
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-07-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `bench(func, *args, **kwargs)` — one-line benchmarking that prints a rich
|
|
14
|
+
report and returns the function's original return value.
|
|
15
|
+
- `@benchmark` decorator (bare and parameterised forms) with `last_result`
|
|
16
|
+
and `original` attributes; a shared re-entrancy guard keeps recursive and
|
|
17
|
+
nested decorated calls un-instrumented, so exactly one report is produced
|
|
18
|
+
per outermost call.
|
|
19
|
+
- `auto()` / `stop_auto()` — automatic benchmarking of every user-defined
|
|
20
|
+
function call, with live per-call lines, a ranked summary table, and
|
|
21
|
+
fnmatch `include` / `exclude` filters.
|
|
22
|
+
- Metrics: wall time (fastest / slowest / average / median / std dev /
|
|
23
|
+
95th percentile), CPU time and CPU %, peak / current / delta memory via
|
|
24
|
+
`tracemalloc`, process RSS, function calls, recursive calls, max recursion
|
|
25
|
+
depth, and GC collections.
|
|
26
|
+
- Modes: `fast` (1 run), `default` (10 runs + 1 warmup),
|
|
27
|
+
`accurate` (100 runs + 5 warmups) — plus explicit `repeat=` / `warmup=`.
|
|
28
|
+
- `compare()` — ranked head-to-head table with relative slowdowns and
|
|
29
|
+
output-equality checking.
|
|
30
|
+
- `estimate_complexity()` — empirical Big-O fitting over
|
|
31
|
+
O(1) … O(n³) with R² scores.
|
|
32
|
+
- Exports: JSON, CSV, and Markdown via `export_result()` /
|
|
33
|
+
`configure(export=...)`.
|
|
34
|
+
- Optional matplotlib graphs: `plot_runtime()`, `plot_memory()`,
|
|
35
|
+
`plot_comparison()` (install extra: `dsabench[viz]`).
|
|
36
|
+
- Support for coroutine functions (`bench_async` inside running loops),
|
|
37
|
+
generator functions (fresh generator returned to the caller), and
|
|
38
|
+
threaded code (CPU % above 100 is reported faithfully).
|
|
39
|
+
- Fully typed (`py.typed`), Google-style docstrings, 198-test suite,
|
|
40
|
+
GitHub Actions CI (ruff + black + pytest on Python 3.10–3.13) and
|
|
41
|
+
trusted-publishing release workflow.
|
|
42
|
+
|
|
43
|
+
[Unreleased]: https://github.com/priyadip/dsabench/compare/v0.1.0...HEAD
|
|
44
|
+
[0.1.0]: https://github.com/priyadip/dsabench/releases/tag/v0.1.0
|
dsabench-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Priyadip
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dsabench-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dsabench
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Beautiful one-line benchmarking for DSA, competitive programming, and interview prep: time, memory, CPU, calls, recursion, comparisons, and empirical complexity.
|
|
5
|
+
Project-URL: Homepage, https://github.com/priyadip/dsabench
|
|
6
|
+
Project-URL: Repository, https://github.com/priyadip/dsabench
|
|
7
|
+
Project-URL: Issues, https://github.com/priyadip/dsabench/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/priyadip/dsabench/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Priyadip
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: algorithms,benchmark,benchmarking,competitive-programming,complexity,dsa,memory,performance,profiling,timing
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Classifier: Topic :: System :: Benchmark
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: rich>=13.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
31
|
+
Requires-Dist: matplotlib>=3.7; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
35
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: viz
|
|
37
|
+
Requires-Dist: matplotlib>=3.7; extra == 'viz'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# ⚡ DsaBench
|
|
41
|
+
|
|
42
|
+
**One-line benchmarking for DSA, competitive programming, and interview prep.**
|
|
43
|
+
|
|
44
|
+
[](https://github.com/priyadip/dsabench/actions/workflows/ci.yml)
|
|
45
|
+
[](https://pypi.org/project/dsabench/)
|
|
46
|
+
[](https://pypi.org/project/dsabench/)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](https://github.com/psf/black)
|
|
49
|
+
[](https://peps.python.org/pep-0561/)
|
|
50
|
+
|
|
51
|
+
You wrote two solutions to a problem. Which one is faster? How much memory does
|
|
52
|
+
the recursive one eat? How deep does it recurse? Is your "optimised" version
|
|
53
|
+
actually O(n log n)?
|
|
54
|
+
|
|
55
|
+
`dsabench` answers all of that with **one line** — no boilerplate, no
|
|
56
|
+
`timeit` incantations, no manual `tracemalloc` bookkeeping:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from bench import bench
|
|
60
|
+
|
|
61
|
+
answer = bench(solve, arr, target) # runs it, prints the report below,
|
|
62
|
+
print(answer) # ...and still hands back the real answer
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
╭─ ⚡ Benchmark Report ──────────────────────────────────╮
|
|
67
|
+
│ │
|
|
68
|
+
│ Function solve │
|
|
69
|
+
│ Arguments ([3, 1, 2], 4) │
|
|
70
|
+
│ Mode default · 10 runs (+1 warmup) │
|
|
71
|
+
│ Return [1, 3] │
|
|
72
|
+
│ Time │
|
|
73
|
+
│ Fastest 19.293 µs │
|
|
74
|
+
│ Average 22.169 µs │
|
|
75
|
+
│ Median 19.457 µs │
|
|
76
|
+
│ Slowest 46.048 µs │
|
|
77
|
+
│ Std Dev 8.393 µs │
|
|
78
|
+
│ 95th pct 34.303 µs │
|
|
79
|
+
│ Memory │
|
|
80
|
+
│ Peak 2.66 KB │
|
|
81
|
+
│ Current 448 B │
|
|
82
|
+
│ Delta 448 B │
|
|
83
|
+
│ Process RSS 24.95 MB │
|
|
84
|
+
│ CPU │
|
|
85
|
+
│ CPU time 22.680 µs │
|
|
86
|
+
│ CPU % 102.3% │
|
|
87
|
+
│ Calls │
|
|
88
|
+
│ Function calls 465 │
|
|
89
|
+
│ Recursive calls 464 │
|
|
90
|
+
│ Max recursion depth 12 │
|
|
91
|
+
│ GC collections 0 │
|
|
92
|
+
│ │
|
|
93
|
+
╰────────────────────────────────── 2026-07-08T16:53:33 ─╯
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Install
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install dsabench # core (only dependency: rich)
|
|
100
|
+
pip install "dsabench[viz]" # + matplotlib graphs
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Python 3.10+. Fully typed (`py.typed`), MIT licensed.
|
|
104
|
+
|
|
105
|
+
## Three ways to use it
|
|
106
|
+
|
|
107
|
+
### 1 · Function call — `bench()`
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from bench import bench
|
|
111
|
+
|
|
112
|
+
def fib(n):
|
|
113
|
+
return n if n < 2 else fib(n - 1) + fib(n - 2)
|
|
114
|
+
|
|
115
|
+
value = bench(fib, 20) # full report, returns 6765
|
|
116
|
+
value = bench(fib, 20, mode="accurate") # 100 runs + 5 warmups
|
|
117
|
+
value = bench(fib, 20, repeat=50, warmup=2) # explicit control
|
|
118
|
+
value = bench(fib, 20, quiet=True) # measure silently
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`bench()` is transparent: whatever the function returns, you get back —
|
|
122
|
+
including for generator functions (you receive a *fresh* generator) and
|
|
123
|
+
coroutine functions (awaited for you when no event loop is running).
|
|
124
|
+
|
|
125
|
+
### 2 · Decorator — `@benchmark`
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from bench import benchmark
|
|
129
|
+
|
|
130
|
+
@benchmark # bare form
|
|
131
|
+
def solve(nums):
|
|
132
|
+
return sorted(nums)
|
|
133
|
+
|
|
134
|
+
@benchmark(mode="accurate") # parameterised form
|
|
135
|
+
def solve_fast(nums):
|
|
136
|
+
return sorted(nums)
|
|
137
|
+
|
|
138
|
+
solve([3, 1, 2]) # report prints on every call
|
|
139
|
+
solve.last_result.average_ms # latest BenchmarkResult, programmatically
|
|
140
|
+
solve.original([3, 1, 2]) # raw, un-instrumented function
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Recursive and nested decorated functions are handled correctly: inner calls
|
|
144
|
+
run the **original** function, so you get exactly one report per outermost
|
|
145
|
+
call and true recursion statistics (a `@benchmark`-ed `fib(10)` reports 177
|
|
146
|
+
calls at depth 10, not a storm of nested reports).
|
|
147
|
+
|
|
148
|
+
### 3 · Automatic — `auto()`
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from bench import auto, stop_auto
|
|
152
|
+
|
|
153
|
+
auto() # from here on, every user-defined function you call
|
|
154
|
+
# is timed and printed live — stdlib & site-packages
|
|
155
|
+
# are ignored automatically
|
|
156
|
+
|
|
157
|
+
run_my_pipeline()
|
|
158
|
+
|
|
159
|
+
stop_auto() # prints a ranked summary table (calls, total, avg, min, max)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Filter what gets captured with fnmatch patterns:
|
|
163
|
+
`auto(include=["solve*"])` or `auto(exclude=["helper_*"])`.
|
|
164
|
+
|
|
165
|
+
## Compare solutions head-to-head
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from bench import compare
|
|
169
|
+
|
|
170
|
+
result = compare(
|
|
171
|
+
("Memoization", fib_memo),
|
|
172
|
+
("Tabulation", fib_tab),
|
|
173
|
+
("Matrix power", fib_matrix),
|
|
174
|
+
args=(30,),
|
|
175
|
+
)
|
|
176
|
+
result.winner.name # "Matrix power"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
⚡ Comparison — args (30)
|
|
181
|
+
╭──────┬──────────────┬──────────┬──────────┬──────────┬──────────┬──────────────╮
|
|
182
|
+
│ Rank │ Name │ Average │ Fastest │ Std Dev │ Peak Mem │ Relative │
|
|
183
|
+
├──────┼──────────────┼──────────┼──────────┼──────────┼──────────┼──────────────┤
|
|
184
|
+
│ 1 │ Matrix power │ 11.9 µs │ 10.8 µs │ 1.1 µs │ 1.2 KB │ baseline │
|
|
185
|
+
│ 2 │ Memoization │ 38.4 µs │ 35.0 µs │ 2.9 µs │ 9.8 KB │ 3.22× slower │
|
|
186
|
+
│ 3 │ Tabulation │ 51.7 µs │ 48.2 µs │ 3.4 µs │ 2.1 KB │ 4.34× slower │
|
|
187
|
+
╰──────┴──────────────┴──────────┴──────────┴──────────┴──────────┴──────────────╯
|
|
188
|
+
✓ all outputs match
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`compare` also verifies that all candidates return **equal outputs** — the
|
|
192
|
+
fastest wrong answer is still wrong.
|
|
193
|
+
|
|
194
|
+
## Estimate Big-O empirically
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from bench import estimate_complexity
|
|
198
|
+
|
|
199
|
+
estimate_complexity(my_sort, sizes=[1_000, 2_000, 4_000, 8_000, 16_000],
|
|
200
|
+
args_for=lambda n: (random_list(n),))
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
Estimated complexity: O(n log n) (R² = 0.998)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Fits your measured times against O(1), O(log n), O(n), O(n log n), O(n²), and
|
|
208
|
+
O(n³) by least squares and ranks them by R². Treat it as a sanity check, not a
|
|
209
|
+
proof — constant factors and caches are real.
|
|
210
|
+
|
|
211
|
+
## Export and plot
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from bench import run, export_result, plot_runtime
|
|
215
|
+
|
|
216
|
+
result = run(fib, args=(25,)) # like bench(), but returns the
|
|
217
|
+
# BenchmarkResult and never prints
|
|
218
|
+
export_result(result, "fib.json") # .json / .csv / .md by extension
|
|
219
|
+
plot_runtime(result, path="fib.png") # needs dsabench[viz]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Or export everything automatically:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import bench
|
|
226
|
+
bench.configure(export="json", export_dir="benchmarks/")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Configuration
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
import bench
|
|
233
|
+
bench.configure(repeat=100, warmup=5, color=True, memory=True,
|
|
234
|
+
cpu=True, export="json")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Option | Default | Meaning |
|
|
238
|
+
| ------------------ | ----------- | ---------------------------------------------------- |
|
|
239
|
+
| `mode` | `"default"` | `fast` (1 run) / `default` (10+1) / `accurate` (100+5) |
|
|
240
|
+
| `repeat` | mode | Timed repetitions (overrides mode) |
|
|
241
|
+
| `warmup` | mode | Untimed warmup runs (overrides mode) |
|
|
242
|
+
| `color` | `True` | Colored Rich output |
|
|
243
|
+
| `memory` | `True` | Track memory via `tracemalloc` |
|
|
244
|
+
| `cpu` | `True` | Track CPU time / CPU % |
|
|
245
|
+
| `profile` | `True` | Track calls / recursion / GC |
|
|
246
|
+
| `quiet` | `False` | Suppress printed reports |
|
|
247
|
+
| `raise_exceptions` | `True` | Re-raise exceptions from benchmarked code |
|
|
248
|
+
| `export` | `None` | Auto-export every result: `"json"`/`"csv"`/`"md"` |
|
|
249
|
+
| `export_dir` | `"."` | Where auto-exports are written |
|
|
250
|
+
| `precision` | `3` | Decimal places in time formatting |
|
|
251
|
+
| `show_args` | `True` | Include the arguments line in reports |
|
|
252
|
+
|
|
253
|
+
`reset_config()` restores the defaults; `get_config()` returns the active
|
|
254
|
+
configuration.
|
|
255
|
+
|
|
256
|
+
## How measurement works (and why you can trust it)
|
|
257
|
+
|
|
258
|
+
- **Warmups first** (never timed) — caches warm, imports settled.
|
|
259
|
+
- **Clean timed runs** using `time.perf_counter_ns` (wall) and
|
|
260
|
+
`time.process_time_ns` (CPU). Nothing else happens inside the timed region —
|
|
261
|
+
no memory tracking, no call counting, no allocation by dsabench itself.
|
|
262
|
+
- **One separate instrumented pass** afterwards collects memory
|
|
263
|
+
(`tracemalloc`), call counts, recursion depth, and GC activity, so the
|
|
264
|
+
instrumentation overhead never pollutes your timings.
|
|
265
|
+
|
|
266
|
+
That means the default mode calls your function **12 times**: 1 warmup +
|
|
267
|
+
10 timed + 1 instrumented. In `fast` mode it's a single call with everything
|
|
268
|
+
combined (numbers are slightly noisier — that's the trade-off).
|
|
269
|
+
|
|
270
|
+
Other honest details:
|
|
271
|
+
|
|
272
|
+
- CPU % can exceed 100% for multi-threaded code — `process_time_ns` sums all
|
|
273
|
+
threads. That's signal, not a bug.
|
|
274
|
+
- Call counting uses `sys.setprofile`, counts Python-level frames (stdlib
|
|
275
|
+
included, cProfile-style), and excludes dsabench's own machinery. A
|
|
276
|
+
recursion like `fib(10)` reports exactly 177 calls, 176 recursive, depth 10.
|
|
277
|
+
- For coroutine functions, call/recursion tracking is disabled (profile hooks
|
|
278
|
+
and event loops don't mix reliably); timing and memory still work.
|
|
279
|
+
- `tracemalloc` sees Python allocations. C-extension allocations (NumPy
|
|
280
|
+
buffers, etc.) show up in **Process RSS**, not in peak/delta.
|
|
281
|
+
|
|
282
|
+
## Jupyter & async
|
|
283
|
+
|
|
284
|
+
Inside a running event loop (Jupyter, async apps), `bench()` on a coroutine
|
|
285
|
+
can't call `asyncio.run` — use the awaitable form:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
value = await bench_async(fetch_data, url)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The `@benchmark` decorator works on `async def` functions out of the box.
|
|
292
|
+
|
|
293
|
+
## FAQ
|
|
294
|
+
|
|
295
|
+
**Why is the import called `bench` when the package is `dsabench`?**
|
|
296
|
+
`pip install dsabench`, `from bench import bench` — short at the call site,
|
|
297
|
+
unique on PyPI. Heads-up: the Frappe/ERPNext ecosystem ships a CLI tool also
|
|
298
|
+
distributed as `bench` on PyPI. Don't install that package into the same
|
|
299
|
+
environment as dsabench: both would own the `bench` import name and clash.
|
|
300
|
+
|
|
301
|
+
**Do warmup runs affect my numbers?** No — they're executed and discarded
|
|
302
|
+
before the timing loop.
|
|
303
|
+
|
|
304
|
+
**Why do I see 12 executions in default mode?** 1 warmup + 10 timed +
|
|
305
|
+
1 instrumented pass. Functions with side effects (appending to a list,
|
|
306
|
+
writing files) will apply them each time — benchmark pure functions, or pass
|
|
307
|
+
fresh inputs.
|
|
308
|
+
|
|
309
|
+
**Can it benchmark code that raises?** Yes. The exception is captured, shown
|
|
310
|
+
in the report, and re-raised by default (`configure(raise_exceptions=False)`
|
|
311
|
+
to suppress). `run()` never raises target exceptions; check `result.exception`.
|
|
312
|
+
|
|
313
|
+
**Is `auto()` production-safe?** It installs a `sys.setprofile` hook, which
|
|
314
|
+
slows everything down while active. It's a development and learning tool —
|
|
315
|
+
call `stop_auto()` when done.
|
|
316
|
+
|
|
317
|
+
**Timer resolution?** `perf_counter_ns` has nanosecond *units*; actual
|
|
318
|
+
resolution is platform-dependent (typically tens of ns). For micro-functions,
|
|
319
|
+
use `mode="accurate"` and read the median.
|
|
320
|
+
|
|
321
|
+
## Roadmap
|
|
322
|
+
|
|
323
|
+
- [ ] `bench.timeline()` — flame-graph style per-call timeline for `auto()`
|
|
324
|
+
- [ ] Statistical significance testing in `compare()` (Mann–Whitney U)
|
|
325
|
+
- [ ] `--bench` pytest plugin for inline regression benchmarks
|
|
326
|
+
- [ ] HTML report export
|
|
327
|
+
- [ ] Per-line memory attribution (top allocating lines from tracemalloc)
|
|
328
|
+
|
|
329
|
+
## Contributing
|
|
330
|
+
|
|
331
|
+
PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Run
|
|
332
|
+
`ruff check . && black --check . && pytest` before pushing; all three are
|
|
333
|
+
enforced by CI on Python 3.10–3.13.
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
[MIT](LICENSE) © 2026 Priyadip
|