benchstats 3.3.2__tar.gz → 3.4.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.
- {benchstats-3.3.2 → benchstats-3.4.0}/CHANGELOG.md +16 -0
- {benchstats-3.3.2/src/benchstats.egg-info → benchstats-3.4.0}/PKG-INFO +1 -1
- {benchstats-3.3.2 → benchstats-3.4.0}/pyproject.toml +1 -1
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/compare.py +80 -38
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/qbench.py +108 -35
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/render.py +14 -12
- {benchstats-3.3.2 → benchstats-3.4.0/src/benchstats.egg-info}/PKG-INFO +1 -1
- {benchstats-3.3.2 → benchstats-3.4.0}/tests/test_compare.py +5 -2
- {benchstats-3.3.2 → benchstats-3.4.0}/tests/test_qbench.py +58 -17
- {benchstats-3.3.2 → benchstats-3.4.0}/LICENSE +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/MANIFEST.in +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/README.md +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/docs/imgs/simple1.svg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/docs/imgs/simple2.svg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/docs/imgs/simple3.svg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/docs/imgs/simple4.svg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/docs/imgs/simple4_bad.svg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/setup.cfg +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/__init__.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/__main__.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/cli_parser.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/common.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/parser_GbenchJson.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats/parsers.py +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats.egg-info/SOURCES.txt +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats.egg-info/dependency_links.txt +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats.egg-info/entry_points.txt +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats.egg-info/requires.txt +0 -0
- {benchstats-3.3.2 → benchstats-3.4.0}/src/benchstats.egg-info/top_level.txt +0 -0
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# 3.4.0
|
|
2
|
+
- `qbench.showBench()` can take benchmark results as a dict of per-name 2D `[reps, iters]` arrays
|
|
3
|
+
(with `bm_names=None` and a non-empty `alt_delimiter`), not only a single 3D numpy stack.
|
|
4
|
+
- `qbench.showBench()` gained `allow_inplace_reshuffle`: opt in to allow in-place shuffling/copy
|
|
5
|
+
avoidance when bootstrapping or normalizing inputs, instead of always working on a private copy.
|
|
6
|
+
- Restored default value for `qbench.showBench(pvalue_stats_bootstrap=)` to 1000.
|
|
7
|
+
- `CompareStatsResult` now exposes `comparisons` (which original benchmark names each row compares)
|
|
8
|
+
and `comparison_indices` (which rows of the stacked `showBench` input they correspond to; absent
|
|
9
|
+
for dict-input mode), for easier downstream automation.
|
|
10
|
+
|
|
11
|
+
# 3.3.3
|
|
12
|
+
- breaking change, though unlikely important for anyone: due to T-test also being affected by
|
|
13
|
+
bad data, had to rename `compareStats()`'s parameter `brunnermunzel_workaround` to `edge_cases_workaround`
|
|
14
|
+
and extended its effect on t-test.
|
|
15
|
+
- Fix `_at_least_one_differs` flag handling in `CompareStatsResult::updatePvalStats()`.
|
|
16
|
+
|
|
1
17
|
# 3.3.1-.2
|
|
2
18
|
- Changed the order of default metrics of `qbench.showBench()` to make `mean` the leading metric, as
|
|
3
19
|
it's typically the most correct one.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "benchstats"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.4.0"
|
|
8
8
|
description = "Statistical Testing for Benchmark Results Comparison"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Aleksei Rechinskii", email = "5dpea9nhd@mozmail.com" }]
|
|
@@ -39,7 +39,7 @@ class BmCompResult(
|
|
|
39
39
|
def __new__(
|
|
40
40
|
cls,
|
|
41
41
|
res: str,
|
|
42
|
-
|
|
42
|
+
pvalue: float | int,
|
|
43
43
|
v0: float | int | np.ndarray,
|
|
44
44
|
v1: float | int | np.ndarray,
|
|
45
45
|
siz0: int,
|
|
@@ -52,8 +52,8 @@ class BmCompResult(
|
|
|
52
52
|
> when set1 is stochastically greater than set2,
|
|
53
53
|
~ when set1 is not stochastically less or greater than set2
|
|
54
54
|
|
|
55
|
-
-
|
|
56
|
-
|
|
55
|
+
- pvalue is a p-value associated with less or greater comparison result, or a minimum of
|
|
56
|
+
p-values for less/greater comparison
|
|
57
57
|
|
|
58
58
|
- v0/val_set0 and v1/val_set1 are either representative values (mean) of a corresponding
|
|
59
59
|
set, or the whole set iself, depending on `store_sets` flag value of compareStats()
|
|
@@ -62,10 +62,10 @@ class BmCompResult(
|
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
# assert isinstance(rel, bool) and isinstance(pval, (float, np.floating))
|
|
65
|
-
if isinstance(
|
|
66
|
-
|
|
67
|
-
assert isinstance(
|
|
68
|
-
assert 0 <=
|
|
65
|
+
if isinstance(pvalue, int):
|
|
66
|
+
pvalue = float(pvalue)
|
|
67
|
+
assert isinstance(pvalue, kAllowedFpTypes)
|
|
68
|
+
assert 0 <= pvalue <= 1
|
|
69
69
|
if isinstance(v0, int):
|
|
70
70
|
v0 = float(v0)
|
|
71
71
|
if isinstance(v1, int):
|
|
@@ -80,7 +80,7 @@ class BmCompResult(
|
|
|
80
80
|
return super().__new__(
|
|
81
81
|
cls,
|
|
82
82
|
res,
|
|
83
|
-
float(
|
|
83
|
+
float(pvalue),
|
|
84
84
|
v0 if isinstance(v0, np.ndarray) else float(v0),
|
|
85
85
|
v1 if isinstance(v1, np.ndarray) else float(v1),
|
|
86
86
|
siz0,
|
|
@@ -95,10 +95,12 @@ class CompareStatsResult:
|
|
|
95
95
|
method: str,
|
|
96
96
|
alpha: float,
|
|
97
97
|
at_least_one_differs: bool,
|
|
98
|
+
comparisons: dict[str, tuple[str, str]],
|
|
98
99
|
):
|
|
99
100
|
"""Constructor to verify initialization correctness.
|
|
100
|
-
- results
|
|
101
|
+
- results is a mapping {benchmark_name -> {metric_name -> CompResult}}. Keys of the
|
|
101
102
|
top-level dict are common between the data sources sg0 and sg1.
|
|
103
|
+
- comparisons is a mapping {benchmark_name -> (bm_name_in_set0, bm_name_in_set1)}
|
|
102
104
|
"""
|
|
103
105
|
assert isinstance(results, dict) and isinstance(method, str) and method
|
|
104
106
|
assert isinstance(alpha, kAllowedFpTypes)
|
|
@@ -107,6 +109,8 @@ class CompareStatsResult:
|
|
|
107
109
|
self._method: str = method
|
|
108
110
|
self._alpha: float = float(alpha)
|
|
109
111
|
self._at_least_one_differs = bool(at_least_one_differs)
|
|
112
|
+
self._comparisons: dict[str, tuple[str, str]] = comparisons
|
|
113
|
+
self._comparison_indices: dict[str, tuple[int, int]] | None = None
|
|
110
114
|
# {benchmark_name -> {metric_name -> {result_type -> list of pvalues}}}
|
|
111
115
|
self._pval_stats: None | dict[str, dict[str, dict[str, list[float]]]] = None
|
|
112
116
|
|
|
@@ -122,10 +126,33 @@ class CompareStatsResult:
|
|
|
122
126
|
def alpha(self) -> float:
|
|
123
127
|
return self._alpha
|
|
124
128
|
|
|
129
|
+
@property
|
|
130
|
+
def comparisons(self) -> dict[str, tuple[str, str]]:
|
|
131
|
+
"""Returns a mapping between a benchmark name in the results and a tuple of names of the
|
|
132
|
+
original data in the original data sets"""
|
|
133
|
+
return self._comparisons
|
|
134
|
+
|
|
125
135
|
@property
|
|
126
136
|
def at_least_one_differs(self) -> bool:
|
|
137
|
+
"""Returns True if at least one benchmark/metric comparison result is different from "~"
|
|
138
|
+
(no difference) for any metric from `main_metrics` list (or for any metric if `main_metrics`
|
|
139
|
+
is None)"""
|
|
127
140
|
return self._at_least_one_differs
|
|
128
141
|
|
|
142
|
+
@property
|
|
143
|
+
def comparison_indices(self) -> dict[str, tuple[int, int]] | None:
|
|
144
|
+
"""Returns a mapping between a benchmark name in the results and a tuple of indices of the
|
|
145
|
+
original data in the original data sets"""
|
|
146
|
+
return self._comparison_indices
|
|
147
|
+
|
|
148
|
+
def setComparisonIndices(self, comparison_indices: dict[str, tuple[int, int]]):
|
|
149
|
+
"""Sets a mapping between a benchmark name in the results and a tuple of indices of the
|
|
150
|
+
original data of the original data sets (when qbench's form of benchmarks is used)"""
|
|
151
|
+
# assert isinstance(comparison_indices, dict)
|
|
152
|
+
# assert all(isinstance(k, str) and isinstance(v, tuple) and len(v) == 2 and all(isinstance(i, int) for i in v) for k, v in comparison_indices.items())
|
|
153
|
+
# assert frozenset(comparison_indices.keys()) == frozenset(self._comparisons.keys())
|
|
154
|
+
self._comparison_indices = comparison_indices
|
|
155
|
+
|
|
129
156
|
def getMetrics(self) -> tuple[str]:
|
|
130
157
|
return tuple(next(iter(self._results.values())).keys())
|
|
131
158
|
|
|
@@ -134,13 +161,13 @@ class CompareStatsResult:
|
|
|
134
161
|
|
|
135
162
|
def areAllSame(self) -> bool:
|
|
136
163
|
"""Tests if all benchmarks over all metrics compare same"""
|
|
137
|
-
return all(
|
|
164
|
+
return all(
|
|
138
165
|
"~" == cr.result for bm_res in self._results.values() for cr in bm_res.values()
|
|
139
|
-
|
|
166
|
+
)
|
|
140
167
|
|
|
141
168
|
def areMetricsSame(self, metrics: Iterable[str]) -> bool:
|
|
142
169
|
"""Tests if all benchmarks over specified metrics compare same"""
|
|
143
|
-
return all(
|
|
170
|
+
return all("~" == bm_res[m].result for bm_res in self._results.values() for m in metrics)
|
|
144
171
|
|
|
145
172
|
@property
|
|
146
173
|
def pval_stats_available(self) -> bool:
|
|
@@ -162,6 +189,8 @@ class CompareStatsResult:
|
|
|
162
189
|
assert isinstance(other, CompareStatsResult)
|
|
163
190
|
assert other.method == self.method and other.alpha == self.alpha
|
|
164
191
|
assert self._results.keys() == other._results.keys()
|
|
192
|
+
assert self._comparisons == other._comparisons
|
|
193
|
+
self._at_least_one_differs = self._at_least_one_differs or other._at_least_one_differs
|
|
165
194
|
for bm, omdict in other._results.items():
|
|
166
195
|
mdict = self._results[bm]
|
|
167
196
|
assert mdict.keys() == omdict.keys()
|
|
@@ -182,7 +211,7 @@ def poolBenchmarks(
|
|
|
182
211
|
sg0: dict[str, dict[str, Iterable[float]]],
|
|
183
212
|
sg1: dict[str, dict[str, Iterable[float]]] | None,
|
|
184
213
|
logger: LoggingConsole | None = None,
|
|
185
|
-
) -> dict[str, dict[str, dict[str, Iterable[float]]]]:
|
|
214
|
+
) -> tuple[dict[str, dict[str, dict[str, Iterable[float]]]], dict[str, dict[str, str]]]:
|
|
186
215
|
"""Using the specified delimiter divides each benchmark name into (the longest possible) common
|
|
187
216
|
part and an alternative name part, merges the benchmarks from one or two sets of benchmarks into
|
|
188
217
|
a single pool represented by a dictionary where a key specifies common part of benchmark name
|
|
@@ -192,6 +221,9 @@ def poolBenchmarks(
|
|
|
192
221
|
If a benchmark name doesn't contain the delimiter, then its common name is assumed to be a whole
|
|
193
222
|
benchmark name, and its alternative name is assumed to either "0" or "1" depending on the
|
|
194
223
|
benchmark set (sg0 or sg1) it belongs to.
|
|
224
|
+
|
|
225
|
+
Returns a mapping {common_name -> {alternative_name -> {metric_name -> values}}}, and a mapping
|
|
226
|
+
of original names {common_name -> {alternative_name -> original_name}}
|
|
195
227
|
"""
|
|
196
228
|
assert isinstance(alt_delimiter, str) and len(alt_delimiter) > 0
|
|
197
229
|
assert isinstance(sg0, dict) and (isinstance(sg1, dict) or sg1 is None)
|
|
@@ -212,12 +244,15 @@ def poolBenchmarks(
|
|
|
212
244
|
else:
|
|
213
245
|
return bm_name, pfx
|
|
214
246
|
|
|
247
|
+
pool = {}
|
|
248
|
+
original_names = {}
|
|
249
|
+
|
|
215
250
|
def _do_pool(
|
|
216
|
-
pool: dict[str, dict[str, dict[str, Iterable[float]]]],
|
|
217
251
|
sg: dict[str, dict[str, Iterable[float]]],
|
|
218
252
|
pfx: str,
|
|
219
253
|
):
|
|
220
254
|
"""Merges the benchmarks from one set of benchmarks into a pool"""
|
|
255
|
+
nonlocal pool, original_names
|
|
221
256
|
for bm_name, metrics in sg.items():
|
|
222
257
|
common_name, alt_name = splitName(bm_name, pfx)
|
|
223
258
|
cmn_bm = pool.setdefault(common_name, {})
|
|
@@ -229,15 +264,15 @@ def poolBenchmarks(
|
|
|
229
264
|
)
|
|
230
265
|
continue
|
|
231
266
|
cmn_bm[alt_name] = metrics
|
|
267
|
+
original_names.setdefault(common_name, {})[alt_name] = bm_name
|
|
232
268
|
|
|
233
|
-
pool = {}
|
|
234
269
|
if sg1 is None:
|
|
235
|
-
_do_pool(
|
|
270
|
+
_do_pool(sg0, "")
|
|
236
271
|
else:
|
|
237
|
-
_do_pool(
|
|
238
|
-
_do_pool(
|
|
272
|
+
_do_pool(sg0, "0")
|
|
273
|
+
_do_pool(sg1, "1")
|
|
239
274
|
|
|
240
|
-
return pool
|
|
275
|
+
return pool, original_names
|
|
241
276
|
|
|
242
277
|
|
|
243
278
|
def compareStats(
|
|
@@ -246,10 +281,10 @@ def compareStats(
|
|
|
246
281
|
alt_delimiter: str | None = None,
|
|
247
282
|
method: str = next(iter(kMethods.keys())),
|
|
248
283
|
alpha: float = kDefaultAlpha,
|
|
249
|
-
main_metrics: None | list[str] | tuple[str] = None,
|
|
284
|
+
main_metrics: None | str | list[str] | tuple[str] = None,
|
|
250
285
|
debug_log: None | bool | LoggingConsole = True,
|
|
251
286
|
store_sets: bool = False,
|
|
252
|
-
|
|
287
|
+
edge_cases_workaround: None | bool = None,
|
|
253
288
|
) -> CompareStatsResult:
|
|
254
289
|
"""Perform comparison for statistical significance of benchmark results using a chosen
|
|
255
290
|
statistical method.
|
|
@@ -273,29 +308,30 @@ def compareStats(
|
|
|
273
308
|
comparisons: "foo|0bar vs 0baz", "foo|0bar vs 1qux" and "foo|0baz vs 1qux". Numbers 0 and 1 in
|
|
274
309
|
benchmark alternative names references source set of benchmark results (sg0 or sg1).
|
|
275
310
|
|
|
276
|
-
`main_metrics` is either a list/tuple of strings describing containing main metrics
|
|
277
|
-
purpose of computing of at_least_one_differs flag, or None (then all metrics are main)
|
|
311
|
+
`main_metrics` is either a string, a list/tuple of strings describing containing main metrics
|
|
312
|
+
for the purpose of computing of at_least_one_differs flag, or None (then all metrics are main).
|
|
278
313
|
|
|
279
314
|
`debug_log` is a bool, but could also be None (==False) or a logger object like LoggingConsole.
|
|
280
315
|
|
|
281
316
|
`store_sets` is a bool, True will make store whole corresponding dataset into a BmCompResult
|
|
282
317
|
instead of a mean of the set.
|
|
283
318
|
|
|
284
|
-
`
|
|
319
|
+
`edge_cases_workaround`: Formally speaking, Brunner Munzel test method is "underspecified"
|
|
285
320
|
in a sense that by design it doesn't handle a case of non-intersecting sample sets, or sets
|
|
286
321
|
consisting of the same single element (https://aakinshin.net/posts/brunner-munzel-corner-case/).
|
|
287
322
|
scipy.stats.brunnermunzel() implementation at least in versions 1.10-1.15.2 directly follows the
|
|
288
323
|
words of the original paper and doesn't introduce handling for that cases, which causes warnings
|
|
289
324
|
and nans to appear in results. IMO from an engineering point of view this is more like just a
|
|
290
|
-
bug (https://github.com/scipy/scipy/issues/22664).
|
|
291
|
-
handle it: value of None handles the bug only for
|
|
292
|
-
controls whether to handle the bug irrespective of the method
|
|
293
|
-
pvalues for other tests, but this shouldn't affect inference
|
|
294
|
-
value is too wild).
|
|
325
|
+
bug (https://github.com/scipy/scipy/issues/22664). The same issue also applies to Student's
|
|
326
|
+
t-test. `edge_cases_workaround` is a flag to handle it: value of None handles the bug only for
|
|
327
|
+
affected methods, a bool otherwise controls whether to handle the bug irrespective of the method
|
|
328
|
+
used (this might give different pvalues for other tests, but this shouldn't affect inference
|
|
329
|
+
results unless alpha threshold value is too wild).
|
|
330
|
+
It should be noted that for small sample sizes (under 10 elements in any of
|
|
295
331
|
sample sets) this most certainly leads to a wrong p-value result that underestimate a
|
|
296
332
|
probability of error. However, with 10, or preferably more, sample set sizes, this should be
|
|
297
333
|
negligible.
|
|
298
|
-
While
|
|
334
|
+
While edge_cases_workaround allows to disable the workaround, I doubt it has any practical
|
|
299
335
|
value in the context of benchmark results comparison.
|
|
300
336
|
|
|
301
337
|
Returns an instance of CompareStatsResult class
|
|
@@ -306,7 +342,9 @@ def compareStats(
|
|
|
306
342
|
assert isinstance(sg0, dict) and (isinstance(sg1, dict) or (with_alternatives and sg1 is None))
|
|
307
343
|
assert isinstance(method, str) and method in kMethods, "unsupported method"
|
|
308
344
|
assert isinstance(alpha, kAllowedFpTypes) and 0 < alpha and alpha < 0.5
|
|
309
|
-
assert
|
|
345
|
+
assert edge_cases_workaround is None or isinstance(edge_cases_workaround, bool)
|
|
346
|
+
if isinstance(main_metrics, str):
|
|
347
|
+
main_metrics = (main_metrics,)
|
|
310
348
|
assert main_metrics is None or isinstance(main_metrics, (list, tuple))
|
|
311
349
|
|
|
312
350
|
if debug_log is None or (isinstance(debug_log, bool) and not debug_log):
|
|
@@ -317,8 +355,8 @@ def compareStats(
|
|
|
317
355
|
logger = debug_log
|
|
318
356
|
debug_log = True
|
|
319
357
|
|
|
320
|
-
if
|
|
321
|
-
|
|
358
|
+
if edge_cases_workaround is None:
|
|
359
|
+
edge_cases_workaround = method in ("brunnermunzel", "ttest_ind")
|
|
322
360
|
|
|
323
361
|
def warn(*args, **kwargs):
|
|
324
362
|
if debug_log:
|
|
@@ -330,11 +368,11 @@ def compareStats(
|
|
|
330
368
|
|
|
331
369
|
def computePValues(s0, s1):
|
|
332
370
|
"""Computes and return pvalues of s0 being stochastically less or greater than s1"""
|
|
333
|
-
if
|
|
371
|
+
if edge_cases_workaround:
|
|
334
372
|
# test for edge cases that brunnermunzel can't handle properly
|
|
335
373
|
mn0, mx0, mn1, mx1 = np.min(s0), np.max(s0), np.min(s1), np.max(s1)
|
|
336
374
|
assert np.all(np.isfinite([mn0, mx0, mn1, mx1])) # sanity check, more asserts below
|
|
337
|
-
all_eq =
|
|
375
|
+
all_eq = all([mn0 == mx0, mn0 == mn1, mn0 == mx1])
|
|
338
376
|
all_less, all_greater = mx0 < mn1, mn0 > mx1
|
|
339
377
|
|
|
340
378
|
if all_eq:
|
|
@@ -348,7 +386,7 @@ def compareStats(
|
|
|
348
386
|
res_greater = stat_func(s0, s1, alternative="greater")
|
|
349
387
|
|
|
350
388
|
less_pvalue, greater_pvalue = res_less.pvalue, res_greater.pvalue
|
|
351
|
-
if
|
|
389
|
+
if edge_cases_workaround:
|
|
352
390
|
assert np.all(np.isfinite([less_pvalue, greater_pvalue]))
|
|
353
391
|
|
|
354
392
|
return less_pvalue, greater_pvalue
|
|
@@ -447,9 +485,10 @@ def compareStats(
|
|
|
447
485
|
return bm_results
|
|
448
486
|
|
|
449
487
|
results = {}
|
|
488
|
+
comparisons: dict[str, tuple[str, str]] = {} # {comparison_name -> (bm_name0, bm_name1)}
|
|
450
489
|
|
|
451
490
|
if with_alternatives:
|
|
452
|
-
pool = poolBenchmarks(alt_delimiter, sg0, sg1, logger if debug_log else None)
|
|
491
|
+
pool, orig_names = poolBenchmarks(alt_delimiter, sg0, sg1, logger if debug_log else None)
|
|
453
492
|
|
|
454
493
|
for common_name, alternatives in pool.items():
|
|
455
494
|
if len(alternatives) <= 1:
|
|
@@ -460,12 +499,14 @@ def compareStats(
|
|
|
460
499
|
)
|
|
461
500
|
continue
|
|
462
501
|
|
|
502
|
+
onames = orig_names[common_name]
|
|
463
503
|
for altA, altB in itertools.combinations(alternatives.keys(), 2):
|
|
464
504
|
assert isinstance(altA, str) and isinstance(altB, str)
|
|
465
505
|
assert altA != altB
|
|
466
506
|
bm_name = f"{common_name} {alt_delimiter} {altA} vs {altB}"
|
|
467
507
|
assert bm_name not in results
|
|
468
508
|
results[bm_name] = compareBenchmark(bm_name, alternatives[altA], alternatives[altB])
|
|
509
|
+
comparisons[bm_name] = (onames[altA], onames[altB])
|
|
469
510
|
|
|
470
511
|
else:
|
|
471
512
|
common_bms = sg0.keys() & sg1.keys()
|
|
@@ -483,5 +524,6 @@ def compareStats(
|
|
|
483
524
|
warn("Key/benchmark name '%s' not found in set2", bm_name)
|
|
484
525
|
continue
|
|
485
526
|
results[bm_name] = compareBenchmark(bm_name, metrics1, sg1[bm_name])
|
|
527
|
+
comparisons[bm_name] = (bm_name, bm_name)
|
|
486
528
|
|
|
487
|
-
return CompareStatsResult(results, method, alpha, bool(at_least_one_differs))
|
|
529
|
+
return CompareStatsResult(results, method, alpha, bool(at_least_one_differs), comparisons)
|
|
@@ -6,6 +6,7 @@ of Python callables. Some parts, however, could be reused outside of Python benc
|
|
|
6
6
|
import argparse
|
|
7
7
|
from collections import namedtuple
|
|
8
8
|
from collections.abc import Callable, Iterable
|
|
9
|
+
from copy import deepcopy
|
|
9
10
|
import itertools
|
|
10
11
|
import inspect
|
|
11
12
|
import numpy as np
|
|
@@ -66,7 +67,8 @@ class BenchmarkDescription(
|
|
|
66
67
|
args_func: Callable[[], Iterable] | None = None,
|
|
67
68
|
clear_cache_func: Callable[[Iterable], None] | None = None,
|
|
68
69
|
*,
|
|
69
|
-
wait_complete
|
|
70
|
+
# wait_complete sets the default value for the other 2 below
|
|
71
|
+
wait_complete: Callable[[Any], Any] | None = None,
|
|
70
72
|
wait_arg_complete: Callable[[Any], Any] | None = None,
|
|
71
73
|
wait_func_complete: Callable[[Any], Any] | None = None,
|
|
72
74
|
):
|
|
@@ -362,7 +364,7 @@ def bench2(func1, func2, **kwargs):
|
|
|
362
364
|
|
|
363
365
|
def resultsToDict(
|
|
364
366
|
results: np.ndarray, bm_names: tuple | list | str = "code", alt_delimiter: str | None = None
|
|
365
|
-
) -> tuple[dict[str, np.ndarray], str]:
|
|
367
|
+
) -> tuple[dict[str, np.ndarray], str, dict[str, str]]:
|
|
366
368
|
"""Store benchmark results in a dictionary benchmark_name -> benchmark_results
|
|
367
369
|
|
|
368
370
|
Arguments:
|
|
@@ -385,11 +387,13 @@ def resultsToDict(
|
|
|
385
387
|
of functions benchmarked (results.shape[0]), where each string is parsed
|
|
386
388
|
according to the expected format: "<common_name>{alt_delimiter}<alternative_name>".
|
|
387
389
|
Benchmarks with the same "<common_name>" are compared pairwise.
|
|
388
|
-
Returns a tuple of
|
|
390
|
+
Returns a tuple of 3 objects:
|
|
389
391
|
dict[str, np.ndarray]: A dictionary mapping benchmark names to benchmark results as 2D
|
|
390
392
|
matrix of shape (reps, iters). Note that matricies returned are views into the original
|
|
391
393
|
results array, so modifying them will modify the original results array as well.
|
|
392
394
|
str - possibly modified alt_delimiter value
|
|
395
|
+
dict[str, int] - mapping of created benchmark names to indices of benchmark data in the
|
|
396
|
+
results array
|
|
393
397
|
"""
|
|
394
398
|
if results.ndim == 2:
|
|
395
399
|
results = np.expand_dims(results, axis=0)
|
|
@@ -406,13 +410,17 @@ def resultsToDict(
|
|
|
406
410
|
n_names = len(bm_names)
|
|
407
411
|
assert n_names > 0, bm_names_err
|
|
408
412
|
|
|
409
|
-
|
|
413
|
+
sg = {}
|
|
414
|
+
bm_idxs = {}
|
|
415
|
+
|
|
416
|
+
def addBenchmark(bm_name: str, b_idx: int):
|
|
417
|
+
nonlocal sg
|
|
410
418
|
assert bm_name not in sg, (
|
|
411
419
|
f"Duplicate benchmark name '{bm_name}' found. Please provide unique names."
|
|
412
420
|
)
|
|
413
421
|
sg[bm_name] = results[b_idx, :, :] # .copy()
|
|
422
|
+
bm_idxs[bm_name] = b_idx
|
|
414
423
|
|
|
415
|
-
sg = {}
|
|
416
424
|
if alt_delimiter is None:
|
|
417
425
|
if n_funcs % n_names != 0:
|
|
418
426
|
raise ValueError(
|
|
@@ -423,8 +431,8 @@ def resultsToDict(
|
|
|
423
431
|
alt_delimiter = "|"
|
|
424
432
|
|
|
425
433
|
if n_funcs_per_bm <= 1:
|
|
426
|
-
addBenchmark(
|
|
427
|
-
addBenchmark(
|
|
434
|
+
addBenchmark(f"{bm_names[0]}{alt_delimiter}0", 0)
|
|
435
|
+
addBenchmark(f"{bm_names[0]}{alt_delimiter}same", 0)
|
|
428
436
|
else:
|
|
429
437
|
combination_set = list(itertools.combinations(range(n_funcs_per_bm), 2))
|
|
430
438
|
func_group_idx = 0
|
|
@@ -432,10 +440,10 @@ def resultsToDict(
|
|
|
432
440
|
for cs in combination_set:
|
|
433
441
|
n1 = f"{bm_name}{alt_delimiter}{cs[0]}"
|
|
434
442
|
if n1 not in sg:
|
|
435
|
-
addBenchmark(
|
|
443
|
+
addBenchmark(n1, func_group_idx + cs[0])
|
|
436
444
|
n2 = f"{bm_name}{alt_delimiter}{cs[1]}"
|
|
437
445
|
if n2 not in sg:
|
|
438
|
-
addBenchmark(
|
|
446
|
+
addBenchmark(n2, func_group_idx + cs[1])
|
|
439
447
|
func_group_idx += n_funcs_per_bm
|
|
440
448
|
|
|
441
449
|
else:
|
|
@@ -453,9 +461,9 @@ def resultsToDict(
|
|
|
453
461
|
raise ValueError(
|
|
454
462
|
f"Duplicate benchmark name '{bm_name}' found. Please provide unique names."
|
|
455
463
|
)
|
|
456
|
-
addBenchmark(
|
|
464
|
+
addBenchmark(bm_name, b_idx)
|
|
457
465
|
|
|
458
|
-
return sg, alt_delimiter
|
|
466
|
+
return sg, alt_delimiter, bm_idxs
|
|
459
467
|
|
|
460
468
|
|
|
461
469
|
def _splitCompareStats_and_renderArgs(
|
|
@@ -495,24 +503,31 @@ def _splitCompareStats_and_renderArgs(
|
|
|
495
503
|
|
|
496
504
|
|
|
497
505
|
def showBench(
|
|
498
|
-
results: np.ndarray,
|
|
506
|
+
results: np.ndarray | dict[str, np.ndarray],
|
|
499
507
|
*,
|
|
500
|
-
bm_names: tuple | list | str = "code",
|
|
508
|
+
bm_names: tuple | list | str | None = "code",
|
|
501
509
|
alt_delimiter: str | None = None,
|
|
502
510
|
metrics: dict = {"mean": np.mean, "min": np.min},
|
|
503
511
|
console: None | LoggingConsole = None,
|
|
504
|
-
pvalue_stats_bootstrap: int =
|
|
512
|
+
pvalue_stats_bootstrap: int = 1000,
|
|
505
513
|
pvalue_stats_bootstrap_seed: Any = None,
|
|
514
|
+
start_with_reshuffled: bool = False,
|
|
506
515
|
show_progress_each: int = 1,
|
|
507
516
|
render_report: bool = True,
|
|
517
|
+
same_console_for_progress: bool = False,
|
|
518
|
+
allow_inplace_reshuffle: bool = False,
|
|
508
519
|
**kwCompareStats_and_renderArgs,
|
|
509
520
|
) -> CompareStatsResult:
|
|
510
521
|
"""
|
|
511
522
|
Displays the benchmark results in a human-readable format.
|
|
512
523
|
|
|
513
524
|
Args:
|
|
514
|
-
- results (np.ndarray): The benchmark results as a 3D
|
|
515
|
-
numpy array. Essentially, the output of the
|
|
525
|
+
- results (np.ndarray | dict[str, np.ndarray]): The benchmark results as a 3D
|
|
526
|
+
[nfuncs, reps, iters] or 2D [reps, iters] numpy array. Essentially, the output of the
|
|
527
|
+
bench() function.
|
|
528
|
+
Alternatively, could be a dictionary mapping benchmark names to benchmark results as 2D
|
|
529
|
+
matrix of potentially different shapes (reps, iters). In that case, `bm_names` must be None
|
|
530
|
+
and `alt_delimiter` must be a valid delimiter string.
|
|
516
531
|
- bm_names (tuple|list|str, optional) and alt_delimiter: (str|None, optional): defines the
|
|
517
532
|
names of the benchmarks and how they are compared one against the other. Options are:
|
|
518
533
|
- if alt_delimiter is None:
|
|
@@ -546,12 +561,28 @@ def showBench(
|
|
|
546
561
|
assumption of the statistical tests.
|
|
547
562
|
Note that large values like 1000 are computationally expensive, but provide more stable
|
|
548
563
|
results thanks to a better coverage.
|
|
564
|
+
By default the first comparison is computed on the original results and the rest
|
|
565
|
+
`pvalue_stats_bootstrap` comparisons are computed on reshuffled results. This can be changed
|
|
566
|
+
by setting `start_with_reshuffled` to True, which make even the first comparison computed on
|
|
567
|
+
reshuffled results, doing `pvalue_stats_bootstrap - 1` iterations of bootstrapping in total.
|
|
549
568
|
- pvalue_stats_bootstrap_seed (Any, default None): if pvalue bootstrapping is enabled, this seed
|
|
550
569
|
will initialize the random number generator for bootstrapping.
|
|
570
|
+
- start_with_reshuffled (bool, False by default): if True and bootstrapping is enabled, even the
|
|
571
|
+
first comparison will be computed on reshuffled results. Otherwise it is ignored.
|
|
572
|
+
Note that benchmarking results rendering doesn't currently support judging the comparison
|
|
573
|
+
result by the p-value statistics (it always use results of the first comparison instead), so
|
|
574
|
+
it's not recommended to use this option if your code or rendered results interpretation
|
|
575
|
+
doesn't have an additional logic to take the bootstrapped results into account.
|
|
551
576
|
- show_progress_each - if p-value bootstrapping is enabled and it's a positive integer,
|
|
552
577
|
show progress bars and update them each that many bootstrapping iterations.
|
|
578
|
+
- same_console_for_progress (bool, False by default): If True, use the same console for progress
|
|
579
|
+
bar. Useful for using in scripts, but is disabled by default to prevent effect on exported
|
|
580
|
+
reports.
|
|
553
581
|
- render_report (bool, True by default): If False, only returns CompareStatsResult object,
|
|
554
582
|
but doesn't print the report.
|
|
583
|
+
- allow_inplace_reshuffle (bool, False by default): If True, allow the `results` to be
|
|
584
|
+
reshuffled for bootstrapping (or if .ndim < 3 for array argument) in place. Otherwise,
|
|
585
|
+
a copy of the results is made when necessary.
|
|
555
586
|
- kwCompareStats_and_renderArgs: Any optional arguments of the compareStats()
|
|
556
587
|
or renderComparisonResults() functions. By default compareStats() also gets
|
|
557
588
|
store_sets=True argument, and renderComparisonResults() gets
|
|
@@ -567,18 +598,30 @@ def showBench(
|
|
|
567
598
|
kwCompareStats_and_renderArgs["debug_log"] = False
|
|
568
599
|
assert console is None or isinstance(console, LoggingConsole)
|
|
569
600
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
results = np.expand_dims(results, axis=0)
|
|
601
|
+
results_is_dict = isinstance(results, dict)
|
|
602
|
+
if results_is_dict:
|
|
603
|
+
assert bm_names is None and isinstance(alt_delimiter, str) and len(alt_delimiter) > 0
|
|
604
|
+
assert all(isinstance(r, np.ndarray) and r.ndim == 2 for r in results.values())
|
|
575
605
|
|
|
576
|
-
|
|
606
|
+
if not allow_inplace_reshuffle and (
|
|
607
|
+
pvalue_stats_bootstrap > 0 or (not results_is_dict and results.ndim < 3)
|
|
608
|
+
):
|
|
609
|
+
results = deepcopy(results)
|
|
610
|
+
if not results_is_dict and results.ndim < 3:
|
|
611
|
+
results = np.expand_dims(results, axis=0)
|
|
612
|
+
assert results_is_dict or results.ndim == 3, "results must be a dict or 3D numpy array."
|
|
613
|
+
|
|
614
|
+
if pvalue_stats_bootstrap > 0:
|
|
615
|
+
show_progress = isinstance(show_progress_each, int) and show_progress_each > 0
|
|
577
616
|
|
|
578
617
|
compStats_args, render_args = _splitCompareStats_and_renderArgs(
|
|
579
618
|
kwCompareStats_and_renderArgs, console
|
|
580
619
|
)
|
|
581
|
-
|
|
620
|
+
|
|
621
|
+
if results_is_dict:
|
|
622
|
+
resd = results
|
|
623
|
+
else:
|
|
624
|
+
resd, alt_delimiter, bm_idxs = resultsToDict(results, bm_names, alt_delimiter)
|
|
582
625
|
|
|
583
626
|
def _applyMetrics(r: dict) -> dict:
|
|
584
627
|
return {
|
|
@@ -586,24 +629,41 @@ def showBench(
|
|
|
586
629
|
for bm_name, res in r.items()
|
|
587
630
|
}
|
|
588
631
|
|
|
632
|
+
rng = None
|
|
633
|
+
if pvalue_stats_bootstrap > 0 and start_with_reshuffled:
|
|
634
|
+
pvalue_stats_bootstrap -= 1
|
|
635
|
+
if pvalue_stats_bootstrap <= 0 and console is not None:
|
|
636
|
+
console.warning(
|
|
637
|
+
"pvalue_stats_bootstrap is 0, so no bootstrapping will be done even though start_with_reshuffled is True."
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
rng = np.random.default_rng(pvalue_stats_bootstrap_seed)
|
|
641
|
+
for res in resd.values():
|
|
642
|
+
rng.shuffle(res.reshape(-1))
|
|
643
|
+
|
|
589
644
|
sg = _applyMetrics(resd)
|
|
590
645
|
sr = compareStats(sg, None, alt_delimiter=alt_delimiter, **compStats_args)
|
|
591
646
|
|
|
592
647
|
if pvalue_stats_bootstrap > 0:
|
|
593
|
-
rng
|
|
594
|
-
|
|
648
|
+
if rng is None:
|
|
649
|
+
rng = np.random.default_rng(pvalue_stats_bootstrap_seed)
|
|
595
650
|
|
|
596
651
|
if show_progress:
|
|
597
|
-
progress = Progress(
|
|
652
|
+
progress = Progress(
|
|
653
|
+
transient=True, console=console if same_console_for_progress else None
|
|
654
|
+
)
|
|
598
655
|
task = progress.add_task("P-value bootstrapping", total=pvalue_stats_bootstrap)
|
|
599
656
|
progress.start()
|
|
600
657
|
|
|
658
|
+
bs_compStats_args = compStats_args.copy()
|
|
659
|
+
bs_compStats_args["store_sets"] = False
|
|
660
|
+
bs_compStats_args["debug_log"] = False
|
|
661
|
+
|
|
601
662
|
for i in range(pvalue_stats_bootstrap):
|
|
602
|
-
for
|
|
663
|
+
for res in resd.values():
|
|
603
664
|
rng.shuffle(res.reshape(-1))
|
|
604
|
-
assert res.shape == (reps, iters)
|
|
605
665
|
sg = _applyMetrics(resd)
|
|
606
|
-
cs = compareStats(sg, None, alt_delimiter=alt_delimiter, **
|
|
666
|
+
cs = compareStats(sg, None, alt_delimiter=alt_delimiter, **bs_compStats_args)
|
|
607
667
|
sr.updatePvalStats(cs)
|
|
608
668
|
|
|
609
669
|
if show_progress and i % show_progress_each == 0:
|
|
@@ -612,6 +672,11 @@ def showBench(
|
|
|
612
672
|
if show_progress:
|
|
613
673
|
progress.stop()
|
|
614
674
|
|
|
675
|
+
if not results_is_dict:
|
|
676
|
+
sr.setComparisonIndices({
|
|
677
|
+
bm_name: (bm_idxs[bm0], bm_idxs[bm1]) for bm_name, (bm0, bm1) in sr.comparisons.items()
|
|
678
|
+
})
|
|
679
|
+
|
|
615
680
|
if render_report:
|
|
616
681
|
renderComparisonResults(sr, console=console, **render_args)
|
|
617
682
|
return sr
|
|
@@ -731,32 +796,32 @@ def makeArgumentParser(parser=None, *, allow_exports=True):
|
|
|
731
796
|
"--iters",
|
|
732
797
|
type=int,
|
|
733
798
|
default=_g_bench_defaults["iters"],
|
|
734
|
-
help=
|
|
799
|
+
help="Iterations per repetition (default: %(default)s)",
|
|
735
800
|
)
|
|
736
801
|
parser.add_argument(
|
|
737
802
|
"--reps",
|
|
738
803
|
type=int,
|
|
739
804
|
default=_g_bench_defaults["reps"],
|
|
740
|
-
help=
|
|
805
|
+
help="Number of repetitions (default: %(default)s)",
|
|
741
806
|
)
|
|
742
807
|
parser.add_argument(
|
|
743
808
|
"--warmup",
|
|
744
809
|
type=int,
|
|
745
810
|
default=_g_bench_defaults["warmup"],
|
|
746
|
-
help=
|
|
811
|
+
help="Number of warmup iterations (default: %(default)s)",
|
|
747
812
|
)
|
|
748
813
|
parser.add_argument(
|
|
749
814
|
"--batch_functions",
|
|
750
815
|
action=argparse.BooleanOptionalAction,
|
|
751
816
|
default=_g_bench_defaults["batch_functions"],
|
|
752
817
|
help="If true: outer loop - benchmarks, inner loop - iterations. Otherwise reversed: "
|
|
753
|
-
|
|
818
|
+
"outer loop - iterations, inner loop - benchmarks. (default: %(default)s)",
|
|
754
819
|
)
|
|
755
820
|
parser.add_argument(
|
|
756
821
|
"--randomize_iterations",
|
|
757
822
|
action=argparse.BooleanOptionalAction,
|
|
758
823
|
default=_g_bench_defaults["randomize_iterations"],
|
|
759
|
-
help=
|
|
824
|
+
help="Randomly shuffle benchmarks order (default: %(default)s)",
|
|
760
825
|
)
|
|
761
826
|
|
|
762
827
|
parser.add_argument(
|
|
@@ -764,7 +829,15 @@ def makeArgumentParser(parser=None, *, allow_exports=True):
|
|
|
764
829
|
type=int,
|
|
765
830
|
default=_g_showBench_defaults["pvalue_stats_bootstrap"],
|
|
766
831
|
help="Number of iterations for pvalue statistics bootstrapping (default: "
|
|
767
|
-
|
|
832
|
+
"%(default)s). Set to 0 to disable.",
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
parser.add_argument(
|
|
836
|
+
"--start_with_reshuffled",
|
|
837
|
+
action=argparse.BooleanOptionalAction,
|
|
838
|
+
default=_g_showBench_defaults["start_with_reshuffled"],
|
|
839
|
+
help="If true, start even the first pvalue statistics bootstrapping iteration with "
|
|
840
|
+
"reshuffled results (default: %(default)s).",
|
|
768
841
|
)
|
|
769
842
|
|
|
770
843
|
if allow_exports:
|
|
@@ -132,7 +132,7 @@ def renderComparisonResults(
|
|
|
132
132
|
dark_theme: bool = True,
|
|
133
133
|
title: None | bool | str = True, # None, False - disables title, str - customizes it
|
|
134
134
|
style_overrides: dict = None, # overrides for kDefaultStyles
|
|
135
|
-
main_metrics: Iterable[str] = None,
|
|
135
|
+
main_metrics: str | Iterable[str] | None = None,
|
|
136
136
|
show_sample_sizes: bool = False,
|
|
137
137
|
sample_stats=None, # or iterable with predefined values: float%, or from kPossibleStatNames.keys()
|
|
138
138
|
expect_same: bool = False, # if true, show stats from assumption h0 is true
|
|
@@ -183,17 +183,6 @@ def renderComparisonResults(
|
|
|
183
183
|
)
|
|
184
184
|
assert title is None or isinstance(title, str)
|
|
185
185
|
|
|
186
|
-
if show_pvalue_stats:
|
|
187
|
-
if not comp_res.pval_stats_available:
|
|
188
|
-
show_pvalue_stats = False
|
|
189
|
-
console.debug("P-value statistics are not available, disabling show_pvalue_stats")
|
|
190
|
-
else:
|
|
191
|
-
pvs_iters = None
|
|
192
|
-
pvs_styles = [ # indexed by is_main
|
|
193
|
-
[_getFmt(f"metric_{fld}_diff" if i < 2 else f"metric_{fld}_same") for i in range(3)]
|
|
194
|
-
for fld in ("scnd", "main")
|
|
195
|
-
]
|
|
196
|
-
|
|
197
186
|
delim_space = "\n" if multiline else " "
|
|
198
187
|
|
|
199
188
|
_column_descr = (
|
|
@@ -203,6 +192,8 @@ def renderComparisonResults(
|
|
|
203
192
|
)
|
|
204
193
|
|
|
205
194
|
metrics = comp_res.getMetrics()
|
|
195
|
+
if isinstance(main_metrics, str):
|
|
196
|
+
main_metrics = (main_metrics,)
|
|
206
197
|
if main_metrics is None or len(main_metrics) < 1:
|
|
207
198
|
main_metrics = [metrics[0]]
|
|
208
199
|
else:
|
|
@@ -211,6 +202,17 @@ def renderComparisonResults(
|
|
|
211
202
|
scnd_metrics = [m for m in metrics if m not in main_metrics]
|
|
212
203
|
iter_metrics = [*main_metrics, *scnd_metrics]
|
|
213
204
|
|
|
205
|
+
if show_pvalue_stats:
|
|
206
|
+
if not comp_res.pval_stats_available:
|
|
207
|
+
show_pvalue_stats = False
|
|
208
|
+
console.debug("P-value statistics are not available, disabling show_pvalue_stats")
|
|
209
|
+
else:
|
|
210
|
+
pvs_iters = None
|
|
211
|
+
pvs_styles = [ # indexed by is_main
|
|
212
|
+
[_getFmt(f"metric_{fld}_diff" if i < 2 else f"metric_{fld}_same") for i in range(3)]
|
|
213
|
+
for fld in ("scnd", "main")
|
|
214
|
+
]
|
|
215
|
+
|
|
214
216
|
metric_unit_keys = [f"metric_{m}_unit" for m in iter_metrics]
|
|
215
217
|
|
|
216
218
|
assert isinstance(metric_precision, int)
|
|
@@ -9,7 +9,7 @@ kAlpha = bsc.kDefaultAlpha
|
|
|
9
9
|
|
|
10
10
|
class TestCompareStatsResult(unittest.TestCase):
|
|
11
11
|
def test_updatePvalStats(self):
|
|
12
|
-
defargs = (kMethods[0], kAlpha, True)
|
|
12
|
+
defargs = (kMethods[0], kAlpha, True,{})
|
|
13
13
|
cs0 = bsc.CompareStatsResult(
|
|
14
14
|
{
|
|
15
15
|
"b1": {
|
|
@@ -109,4 +109,7 @@ class TestComparisonMethods(unittest.TestCase):
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
if __name__ == "__main__":
|
|
112
|
-
|
|
112
|
+
import sys
|
|
113
|
+
import pytest
|
|
114
|
+
|
|
115
|
+
sys.exit(pytest.main(sys.argv))
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
from typing import Iterable
|
|
3
2
|
import unittest
|
|
4
3
|
import numpy as np
|
|
5
|
-
import pytest
|
|
6
4
|
import time
|
|
7
5
|
import traceback
|
|
8
6
|
|
|
@@ -232,7 +230,7 @@ class TestQBench(unittest.TestCase):
|
|
|
232
230
|
check_reset_counters(7 * 2, 7 * 2, 7 * 2, 0, 7 + 14)
|
|
233
231
|
|
|
234
232
|
def test_showBench_with_delimiter(self):
|
|
235
|
-
def_prms = {"alt_delimiter": "|", "render_report": False}
|
|
233
|
+
def_prms = {"alt_delimiter": "|", "render_report": False, "show_progress_each": 0}
|
|
236
234
|
nreps = 10
|
|
237
235
|
results = np.stack(
|
|
238
236
|
(
|
|
@@ -246,13 +244,24 @@ class TestQBench(unittest.TestCase):
|
|
|
246
244
|
axis=0,
|
|
247
245
|
)
|
|
248
246
|
bm_names = ("alg0|A", "alg0|B", "alg1|A", "alg1|B", "alg2|A", "alg2|B")
|
|
249
|
-
|
|
250
|
-
assert "<" ==
|
|
251
|
-
assert ">" ==
|
|
252
|
-
assert "~" ==
|
|
247
|
+
cr = qb.showBench(results, bm_names=bm_names, **def_prms)
|
|
248
|
+
assert "<" == cr.results["alg0 | A vs B"]["mean"].result
|
|
249
|
+
assert ">" == cr.results["alg1 | A vs B"]["mean"].result
|
|
250
|
+
assert "~" == cr.results["alg2 | A vs B"]["mean"].result
|
|
251
|
+
assert cr.at_least_one_differs
|
|
252
|
+
assert cr.comparisons == {
|
|
253
|
+
"alg0 | A vs B": ("alg0|A", "alg0|B"),
|
|
254
|
+
"alg1 | A vs B": ("alg1|A", "alg1|B"),
|
|
255
|
+
"alg2 | A vs B": ("alg2|A", "alg2|B"),
|
|
256
|
+
}
|
|
257
|
+
assert cr.comparison_indices == {
|
|
258
|
+
"alg0 | A vs B": (0, 1),
|
|
259
|
+
"alg1 | A vs B": (2, 3),
|
|
260
|
+
"alg2 | A vs B": (4, 5),
|
|
261
|
+
}
|
|
253
262
|
|
|
254
263
|
def test_showBench_one_name(self):
|
|
255
|
-
def_prms = {"render_report": False}
|
|
264
|
+
def_prms = {"render_report": False, "show_progress_each": 0}
|
|
256
265
|
nreps = 10
|
|
257
266
|
results = np.stack(
|
|
258
267
|
(
|
|
@@ -262,13 +271,24 @@ class TestQBench(unittest.TestCase):
|
|
|
262
271
|
),
|
|
263
272
|
axis=0,
|
|
264
273
|
)
|
|
265
|
-
|
|
266
|
-
assert "<" ==
|
|
267
|
-
assert ">" ==
|
|
268
|
-
assert ">" ==
|
|
274
|
+
cr = qb.showBench(results, bm_names="code", **def_prms)
|
|
275
|
+
assert "<" == cr.results["code | 0 vs 1"]["mean"].result
|
|
276
|
+
assert ">" == cr.results["code | 1 vs 2"]["mean"].result
|
|
277
|
+
assert ">" == cr.results["code | 0 vs 2"]["mean"].result
|
|
278
|
+
assert cr.at_least_one_differs
|
|
279
|
+
assert cr.comparisons == {
|
|
280
|
+
"code | 0 vs 1": ("code|0", "code|1"),
|
|
281
|
+
"code | 1 vs 2": ("code|1", "code|2"),
|
|
282
|
+
"code | 0 vs 2": ("code|0", "code|2"),
|
|
283
|
+
}
|
|
284
|
+
assert cr.comparison_indices == {
|
|
285
|
+
"code | 0 vs 1": (0, 1),
|
|
286
|
+
"code | 1 vs 2": (1, 2),
|
|
287
|
+
"code | 0 vs 2": (0, 2),
|
|
288
|
+
}
|
|
269
289
|
|
|
270
290
|
def test_showBench_tuple(self):
|
|
271
|
-
def_prms = {"render_report": False}
|
|
291
|
+
def_prms = {"render_report": False, "show_progress_each": 0}
|
|
272
292
|
nreps = 10
|
|
273
293
|
results = np.stack(
|
|
274
294
|
(
|
|
@@ -282,10 +302,21 @@ class TestQBench(unittest.TestCase):
|
|
|
282
302
|
axis=0,
|
|
283
303
|
)
|
|
284
304
|
bm_names = ("alg0", "alg1", "alg2")
|
|
285
|
-
|
|
286
|
-
assert "<" ==
|
|
287
|
-
assert ">" ==
|
|
288
|
-
assert "~" ==
|
|
305
|
+
cr = qb.showBench(results, bm_names=bm_names, **def_prms)
|
|
306
|
+
assert "<" == cr.results["alg0 | 0 vs 1"]["mean"].result
|
|
307
|
+
assert ">" == cr.results["alg1 | 0 vs 1"]["mean"].result
|
|
308
|
+
assert "~" == cr.results["alg2 | 0 vs 1"]["mean"].result
|
|
309
|
+
assert cr.at_least_one_differs
|
|
310
|
+
assert cr.comparisons == {
|
|
311
|
+
"alg0 | 0 vs 1": ("alg0|0", "alg0|1"),
|
|
312
|
+
"alg1 | 0 vs 1": ("alg1|0", "alg1|1"),
|
|
313
|
+
"alg2 | 0 vs 1": ("alg2|0", "alg2|1"),
|
|
314
|
+
}
|
|
315
|
+
assert cr.comparison_indices == {
|
|
316
|
+
"alg0 | 0 vs 1": (0, 1),
|
|
317
|
+
"alg1 | 0 vs 1": (2, 3),
|
|
318
|
+
"alg2 | 0 vs 1": (4, 5),
|
|
319
|
+
}
|
|
289
320
|
|
|
290
321
|
def test_showBench_pvalue_stats_bootstrap(self):
|
|
291
322
|
r = np.array([[[1, 3, 3], [1, 3, 3]], [[1, 2, 2], [1, 2, 2]]])
|
|
@@ -295,6 +326,8 @@ class TestQBench(unittest.TestCase):
|
|
|
295
326
|
alpha=0.499,
|
|
296
327
|
render_report=False,
|
|
297
328
|
console=None,
|
|
329
|
+
show_progress_each=0,
|
|
330
|
+
start_with_reshuffled=False,
|
|
298
331
|
)
|
|
299
332
|
# seed is chosen to modify the first bootstrap result and produce the same second result
|
|
300
333
|
|
|
@@ -306,12 +339,20 @@ class TestQBench(unittest.TestCase):
|
|
|
306
339
|
assert len(pvs["~"]) == same_len
|
|
307
340
|
assert all(pvs["~"][i] == 1.0 for i in range(same_len))
|
|
308
341
|
|
|
342
|
+
cr = qb.showBench(r, pvalue_stats_bootstrap=0, **cmnargs)
|
|
343
|
+
assert not cr.at_least_one_differs
|
|
344
|
+
|
|
309
345
|
cr = qb.showBench(r, pvalue_stats_bootstrap=1, **cmnargs)
|
|
346
|
+
assert cr.at_least_one_differs
|
|
310
347
|
test_pvs(next(iter(next(iter(cr.pval_stats.values())).values())), 1)
|
|
311
348
|
|
|
312
349
|
cr = qb.showBench(r, pvalue_stats_bootstrap=2, **cmnargs)
|
|
350
|
+
assert cr.at_least_one_differs
|
|
313
351
|
test_pvs(next(iter(next(iter(cr.pval_stats.values())).values())), 2)
|
|
314
352
|
|
|
315
353
|
|
|
316
354
|
if __name__ == "__main__":
|
|
355
|
+
import sys
|
|
356
|
+
import pytest
|
|
357
|
+
|
|
317
358
|
sys.exit(pytest.main(sys.argv))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|