benchstats 3.3.3__tar.gz → 3.4.1__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.3 → benchstats-3.4.1}/CHANGELOG.md +11 -1
- {benchstats-3.3.3/src/benchstats.egg-info → benchstats-3.4.1}/PKG-INFO +1 -1
- {benchstats-3.3.3 → benchstats-3.4.1}/pyproject.toml +1 -1
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/compare.py +65 -25
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/qbench.py +108 -35
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/render.py +15 -12
- {benchstats-3.3.3 → benchstats-3.4.1/src/benchstats.egg-info}/PKG-INFO +1 -1
- {benchstats-3.3.3 → benchstats-3.4.1}/tests/test_compare.py +1 -1
- {benchstats-3.3.3 → benchstats-3.4.1}/tests/test_qbench.py +46 -12
- {benchstats-3.3.3 → benchstats-3.4.1}/LICENSE +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/MANIFEST.in +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/README.md +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/docs/imgs/simple1.svg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/docs/imgs/simple2.svg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/docs/imgs/simple3.svg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/docs/imgs/simple4.svg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/docs/imgs/simple4_bad.svg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/setup.cfg +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/__init__.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/__main__.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/cli_parser.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/common.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/parser_GbenchJson.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats/parsers.py +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats.egg-info/SOURCES.txt +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats.egg-info/dependency_links.txt +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats.egg-info/entry_points.txt +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats.egg-info/requires.txt +0 -0
- {benchstats-3.3.3 → benchstats-3.4.1}/src/benchstats.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,16 @@
|
|
|
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
|
+
|
|
1
11
|
# 3.3.3
|
|
2
12
|
- breaking change, though unlikely important for anyone: due to T-test also being affected by
|
|
3
|
-
bad
|
|
13
|
+
bad data, had to rename `compareStats()`'s parameter `brunnermunzel_workaround` to `edge_cases_workaround`
|
|
4
14
|
and extended its effect on t-test.
|
|
5
15
|
- Fix `_at_least_one_differs` flag handling in `CompareStatsResult::updatePvalStats()`.
|
|
6
16
|
|
|
@@ -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.1"
|
|
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,7 @@ 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
|
|
165
193
|
self._at_least_one_differs = self._at_least_one_differs or other._at_least_one_differs
|
|
166
194
|
for bm, omdict in other._results.items():
|
|
167
195
|
mdict = self._results[bm]
|
|
@@ -183,7 +211,7 @@ def poolBenchmarks(
|
|
|
183
211
|
sg0: dict[str, dict[str, Iterable[float]]],
|
|
184
212
|
sg1: dict[str, dict[str, Iterable[float]]] | None,
|
|
185
213
|
logger: LoggingConsole | None = None,
|
|
186
|
-
) -> dict[str, dict[str, dict[str, Iterable[float]]]]:
|
|
214
|
+
) -> tuple[dict[str, dict[str, dict[str, Iterable[float]]]], dict[str, dict[str, str]]]:
|
|
187
215
|
"""Using the specified delimiter divides each benchmark name into (the longest possible) common
|
|
188
216
|
part and an alternative name part, merges the benchmarks from one or two sets of benchmarks into
|
|
189
217
|
a single pool represented by a dictionary where a key specifies common part of benchmark name
|
|
@@ -193,6 +221,9 @@ def poolBenchmarks(
|
|
|
193
221
|
If a benchmark name doesn't contain the delimiter, then its common name is assumed to be a whole
|
|
194
222
|
benchmark name, and its alternative name is assumed to either "0" or "1" depending on the
|
|
195
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}}
|
|
196
227
|
"""
|
|
197
228
|
assert isinstance(alt_delimiter, str) and len(alt_delimiter) > 0
|
|
198
229
|
assert isinstance(sg0, dict) and (isinstance(sg1, dict) or sg1 is None)
|
|
@@ -213,12 +244,15 @@ def poolBenchmarks(
|
|
|
213
244
|
else:
|
|
214
245
|
return bm_name, pfx
|
|
215
246
|
|
|
247
|
+
pool = {}
|
|
248
|
+
original_names = {}
|
|
249
|
+
|
|
216
250
|
def _do_pool(
|
|
217
|
-
pool: dict[str, dict[str, dict[str, Iterable[float]]]],
|
|
218
251
|
sg: dict[str, dict[str, Iterable[float]]],
|
|
219
252
|
pfx: str,
|
|
220
253
|
):
|
|
221
254
|
"""Merges the benchmarks from one set of benchmarks into a pool"""
|
|
255
|
+
nonlocal pool, original_names
|
|
222
256
|
for bm_name, metrics in sg.items():
|
|
223
257
|
common_name, alt_name = splitName(bm_name, pfx)
|
|
224
258
|
cmn_bm = pool.setdefault(common_name, {})
|
|
@@ -230,15 +264,15 @@ def poolBenchmarks(
|
|
|
230
264
|
)
|
|
231
265
|
continue
|
|
232
266
|
cmn_bm[alt_name] = metrics
|
|
267
|
+
original_names.setdefault(common_name, {})[alt_name] = bm_name
|
|
233
268
|
|
|
234
|
-
pool = {}
|
|
235
269
|
if sg1 is None:
|
|
236
|
-
_do_pool(
|
|
270
|
+
_do_pool(sg0, "")
|
|
237
271
|
else:
|
|
238
|
-
_do_pool(
|
|
239
|
-
_do_pool(
|
|
272
|
+
_do_pool(sg0, "0")
|
|
273
|
+
_do_pool(sg1, "1")
|
|
240
274
|
|
|
241
|
-
return pool
|
|
275
|
+
return pool, original_names
|
|
242
276
|
|
|
243
277
|
|
|
244
278
|
def compareStats(
|
|
@@ -247,7 +281,7 @@ def compareStats(
|
|
|
247
281
|
alt_delimiter: str | None = None,
|
|
248
282
|
method: str = next(iter(kMethods.keys())),
|
|
249
283
|
alpha: float = kDefaultAlpha,
|
|
250
|
-
main_metrics: None | list[str] | tuple[str] = None,
|
|
284
|
+
main_metrics: None | str | list[str] | tuple[str] = None,
|
|
251
285
|
debug_log: None | bool | LoggingConsole = True,
|
|
252
286
|
store_sets: bool = False,
|
|
253
287
|
edge_cases_workaround: None | bool = None,
|
|
@@ -274,8 +308,8 @@ def compareStats(
|
|
|
274
308
|
comparisons: "foo|0bar vs 0baz", "foo|0bar vs 1qux" and "foo|0baz vs 1qux". Numbers 0 and 1 in
|
|
275
309
|
benchmark alternative names references source set of benchmark results (sg0 or sg1).
|
|
276
310
|
|
|
277
|
-
`main_metrics` is either a list/tuple of strings describing containing main metrics
|
|
278
|
-
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).
|
|
279
313
|
|
|
280
314
|
`debug_log` is a bool, but could also be None (==False) or a logger object like LoggingConsole.
|
|
281
315
|
|
|
@@ -309,6 +343,8 @@ def compareStats(
|
|
|
309
343
|
assert isinstance(method, str) and method in kMethods, "unsupported method"
|
|
310
344
|
assert isinstance(alpha, kAllowedFpTypes) and 0 < alpha and alpha < 0.5
|
|
311
345
|
assert edge_cases_workaround is None or isinstance(edge_cases_workaround, bool)
|
|
346
|
+
if isinstance(main_metrics, str):
|
|
347
|
+
main_metrics = (main_metrics,)
|
|
312
348
|
assert main_metrics is None or isinstance(main_metrics, (list, tuple))
|
|
313
349
|
|
|
314
350
|
if debug_log is None or (isinstance(debug_log, bool) and not debug_log):
|
|
@@ -320,7 +356,7 @@ def compareStats(
|
|
|
320
356
|
debug_log = True
|
|
321
357
|
|
|
322
358
|
if edge_cases_workaround is None:
|
|
323
|
-
edge_cases_workaround =
|
|
359
|
+
edge_cases_workaround = method in ("brunnermunzel", "ttest_ind")
|
|
324
360
|
|
|
325
361
|
def warn(*args, **kwargs):
|
|
326
362
|
if debug_log:
|
|
@@ -449,9 +485,10 @@ def compareStats(
|
|
|
449
485
|
return bm_results
|
|
450
486
|
|
|
451
487
|
results = {}
|
|
488
|
+
comparisons: dict[str, tuple[str, str]] = {} # {comparison_name -> (bm_name0, bm_name1)}
|
|
452
489
|
|
|
453
490
|
if with_alternatives:
|
|
454
|
-
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)
|
|
455
492
|
|
|
456
493
|
for common_name, alternatives in pool.items():
|
|
457
494
|
if len(alternatives) <= 1:
|
|
@@ -462,12 +499,14 @@ def compareStats(
|
|
|
462
499
|
)
|
|
463
500
|
continue
|
|
464
501
|
|
|
502
|
+
onames = orig_names[common_name]
|
|
465
503
|
for altA, altB in itertools.combinations(alternatives.keys(), 2):
|
|
466
504
|
assert isinstance(altA, str) and isinstance(altB, str)
|
|
467
505
|
assert altA != altB
|
|
468
506
|
bm_name = f"{common_name} {alt_delimiter} {altA} vs {altB}"
|
|
469
507
|
assert bm_name not in results
|
|
470
508
|
results[bm_name] = compareBenchmark(bm_name, alternatives[altA], alternatives[altB])
|
|
509
|
+
comparisons[bm_name] = (onames[altA], onames[altB])
|
|
471
510
|
|
|
472
511
|
else:
|
|
473
512
|
common_bms = sg0.keys() & sg1.keys()
|
|
@@ -485,5 +524,6 @@ def compareStats(
|
|
|
485
524
|
warn("Key/benchmark name '%s' not found in set2", bm_name)
|
|
486
525
|
continue
|
|
487
526
|
results[bm_name] = compareBenchmark(bm_name, metrics1, sg1[bm_name])
|
|
527
|
+
comparisons[bm_name] = (bm_name, bm_name)
|
|
488
528
|
|
|
489
|
-
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:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
import numpy as np
|
|
5
5
|
import rich
|
|
6
|
+
import rich.table
|
|
6
7
|
from rich.text import Text
|
|
7
8
|
import math
|
|
8
9
|
|
|
@@ -132,7 +133,7 @@ def renderComparisonResults(
|
|
|
132
133
|
dark_theme: bool = True,
|
|
133
134
|
title: None | bool | str = True, # None, False - disables title, str - customizes it
|
|
134
135
|
style_overrides: dict = None, # overrides for kDefaultStyles
|
|
135
|
-
main_metrics: Iterable[str] = None,
|
|
136
|
+
main_metrics: str | Iterable[str] | None = None,
|
|
136
137
|
show_sample_sizes: bool = False,
|
|
137
138
|
sample_stats=None, # or iterable with predefined values: float%, or from kPossibleStatNames.keys()
|
|
138
139
|
expect_same: bool = False, # if true, show stats from assumption h0 is true
|
|
@@ -183,17 +184,6 @@ def renderComparisonResults(
|
|
|
183
184
|
)
|
|
184
185
|
assert title is None or isinstance(title, str)
|
|
185
186
|
|
|
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
187
|
delim_space = "\n" if multiline else " "
|
|
198
188
|
|
|
199
189
|
_column_descr = (
|
|
@@ -203,6 +193,8 @@ def renderComparisonResults(
|
|
|
203
193
|
)
|
|
204
194
|
|
|
205
195
|
metrics = comp_res.getMetrics()
|
|
196
|
+
if isinstance(main_metrics, str):
|
|
197
|
+
main_metrics = (main_metrics,)
|
|
206
198
|
if main_metrics is None or len(main_metrics) < 1:
|
|
207
199
|
main_metrics = [metrics[0]]
|
|
208
200
|
else:
|
|
@@ -211,6 +203,17 @@ def renderComparisonResults(
|
|
|
211
203
|
scnd_metrics = [m for m in metrics if m not in main_metrics]
|
|
212
204
|
iter_metrics = [*main_metrics, *scnd_metrics]
|
|
213
205
|
|
|
206
|
+
if show_pvalue_stats:
|
|
207
|
+
if not comp_res.pval_stats_available:
|
|
208
|
+
show_pvalue_stats = False
|
|
209
|
+
console.debug("P-value statistics are not available, disabling show_pvalue_stats")
|
|
210
|
+
else:
|
|
211
|
+
pvs_iters = None
|
|
212
|
+
pvs_styles = [ # indexed by is_main
|
|
213
|
+
[_getFmt(f"metric_{fld}_diff" if i < 2 else f"metric_{fld}_same") for i in range(3)]
|
|
214
|
+
for fld in ("scnd", "main")
|
|
215
|
+
]
|
|
216
|
+
|
|
214
217
|
metric_unit_keys = [f"metric_{m}_unit" for m in iter_metrics]
|
|
215
218
|
|
|
216
219
|
assert isinstance(metric_precision, int)
|
|
@@ -244,10 +244,21 @@ class TestQBench(unittest.TestCase):
|
|
|
244
244
|
axis=0,
|
|
245
245
|
)
|
|
246
246
|
bm_names = ("alg0|A", "alg0|B", "alg1|A", "alg1|B", "alg2|A", "alg2|B")
|
|
247
|
-
|
|
248
|
-
assert "<" ==
|
|
249
|
-
assert ">" ==
|
|
250
|
-
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
|
+
}
|
|
251
262
|
|
|
252
263
|
def test_showBench_one_name(self):
|
|
253
264
|
def_prms = {"render_report": False, "show_progress_each": 0}
|
|
@@ -260,10 +271,21 @@ class TestQBench(unittest.TestCase):
|
|
|
260
271
|
),
|
|
261
272
|
axis=0,
|
|
262
273
|
)
|
|
263
|
-
|
|
264
|
-
assert "<" ==
|
|
265
|
-
assert ">" ==
|
|
266
|
-
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
|
+
}
|
|
267
289
|
|
|
268
290
|
def test_showBench_tuple(self):
|
|
269
291
|
def_prms = {"render_report": False, "show_progress_each": 0}
|
|
@@ -280,10 +302,21 @@ class TestQBench(unittest.TestCase):
|
|
|
280
302
|
axis=0,
|
|
281
303
|
)
|
|
282
304
|
bm_names = ("alg0", "alg1", "alg2")
|
|
283
|
-
|
|
284
|
-
assert "<" ==
|
|
285
|
-
assert ">" ==
|
|
286
|
-
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
|
+
}
|
|
287
320
|
|
|
288
321
|
def test_showBench_pvalue_stats_bootstrap(self):
|
|
289
322
|
r = np.array([[[1, 3, 3], [1, 3, 3]], [[1, 2, 2], [1, 2, 2]]])
|
|
@@ -294,6 +327,7 @@ class TestQBench(unittest.TestCase):
|
|
|
294
327
|
render_report=False,
|
|
295
328
|
console=None,
|
|
296
329
|
show_progress_each=0,
|
|
330
|
+
start_with_reshuffled=False,
|
|
297
331
|
)
|
|
298
332
|
# seed is chosen to modify the first bootstrap result and produce the same second result
|
|
299
333
|
|
|
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
|