holobench 1.25.1__py3-none-any.whl → 1.26.3__py3-none-any.whl
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.
- bencher/example/benchmark_data.py +196 -0
- bencher/example/example_all.py +45 -0
- bencher/example/example_categorical.py +99 -0
- bencher/example/example_composable_container.py +106 -0
- bencher/example/example_composable_container2.py +160 -0
- bencher/example/example_consts.py +39 -0
- bencher/example/example_custom_sweep.py +59 -0
- bencher/example/example_custom_sweep2.py +42 -0
- bencher/example/example_docs.py +34 -0
- bencher/example/example_filepath.py +27 -0
- bencher/example/example_float3D.py +101 -0
- bencher/example/example_float_cat.py +99 -0
- bencher/example/example_floats.py +89 -0
- bencher/example/example_floats2D.py +93 -0
- bencher/example/example_holosweep.py +98 -0
- bencher/example/example_holosweep_objects.py +111 -0
- bencher/example/example_holosweep_tap.py +144 -0
- bencher/example/example_image.py +155 -0
- bencher/example/example_levels.py +181 -0
- bencher/example/example_levels2.py +37 -0
- bencher/example/example_pareto.py +53 -0
- bencher/example/example_sample_cache.py +85 -0
- bencher/example/example_sample_cache_context.py +116 -0
- bencher/example/example_simple.py +134 -0
- bencher/example/example_simple_bool.py +35 -0
- bencher/example/example_simple_cat.py +48 -0
- bencher/example/example_simple_float.py +28 -0
- bencher/example/example_simple_float2d.py +29 -0
- bencher/example/example_strings.py +47 -0
- bencher/example/example_time_event.py +63 -0
- bencher/example/example_video.py +118 -0
- bencher/example/example_workflow.py +189 -0
- bencher/example/experimental/example_bokeh_plotly.py +38 -0
- bencher/example/experimental/example_hover_ex.py +45 -0
- bencher/example/experimental/example_hvplot_explorer.py +39 -0
- bencher/example/experimental/example_interactive.py +75 -0
- bencher/example/experimental/example_streamnd.py +49 -0
- bencher/example/experimental/example_streams.py +36 -0
- bencher/example/experimental/example_template.py +40 -0
- bencher/example/experimental/example_updates.py +84 -0
- bencher/example/experimental/example_vector.py +84 -0
- bencher/example/meta/example_meta.py +171 -0
- bencher/example/meta/example_meta_cat.py +25 -0
- bencher/example/meta/example_meta_float.py +23 -0
- bencher/example/meta/example_meta_levels.py +26 -0
- bencher/example/optuna/example_optuna.py +78 -0
- bencher/example/shelved/example_float2D_scatter.py +109 -0
- bencher/example/shelved/example_float3D_cone.py +96 -0
- bencher/example/shelved/example_kwargs.py +63 -0
- bencher/plotting/__init__.py +0 -0
- bencher/plotting/plot_filter.py +110 -0
- bencher/plotting/plt_cnt_cfg.py +75 -0
- bencher/results/__init__.py +0 -0
- bencher/results/bench_result.py +94 -0
- bencher/results/bench_result_base.py +476 -0
- bencher/results/composable_container/__init__.py +0 -0
- bencher/results/composable_container/composable_container_base.py +73 -0
- bencher/results/composable_container/composable_container_panel.py +39 -0
- bencher/results/composable_container/composable_container_video.py +184 -0
- bencher/results/float_formatter.py +44 -0
- bencher/results/holoview_result.py +753 -0
- bencher/results/optuna_result.py +354 -0
- bencher/results/panel_result.py +41 -0
- bencher/results/plotly_result.py +65 -0
- bencher/results/video_result.py +38 -0
- bencher/results/video_summary.py +222 -0
- bencher/variables/__init__.py +0 -0
- bencher/variables/inputs.py +202 -0
- bencher/variables/parametrised_sweep.py +208 -0
- bencher/variables/results.py +214 -0
- bencher/variables/sweep_base.py +162 -0
- bencher/variables/time.py +92 -0
- holobench-1.26.3.data/data/share/ament_index/resource_index/packages/bencher +0 -0
- {holobench-1.25.1.dist-info → holobench-1.26.3.dist-info}/METADATA +5 -7
- holobench-1.26.3.dist-info/RECORD +93 -0
- holobench-1.25.1.dist-info/RECORD +0 -20
- /holobench-1.25.1.data/data/share/ament_index/resource_index/packages/bencher → /bencher/example/__init__.py +0 -0
- {holobench-1.25.1.data → holobench-1.26.3.data}/data/share/bencher/package.xml +0 -0
- {holobench-1.25.1.dist-info → holobench-1.26.3.dist-info}/LICENSE +0 -0
- {holobench-1.25.1.dist-info → holobench-1.26.3.dist-info}/WHEEL +0 -0
- {holobench-1.25.1.dist-info → holobench-1.26.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
import bencher as bch
|
2
|
+
|
3
|
+
|
4
|
+
class UnreliableClass(bch.ParametrizedSweep):
|
5
|
+
"""This class helps demonstrate benchmarking a function that sometimes crashes during sampling. By using BenchRunCfg.use_sample_cache you can store the results of every call to the benchmark function so data is not lost in the event of a crash. However, because cache invalidation is hard (https://martinfowler.com/bliki/TwoHardThings.html) you need to be mindful of how you could get bad results due to incorrect cache data. For example if you change your benchmark function and use the sample cache you will not get correct values; you will need to use BenchRunCfg.clear_sample_cache to purge any out of date results."""
|
6
|
+
|
7
|
+
input_val = bch.IntSweep(
|
8
|
+
default=0,
|
9
|
+
bounds=[0, 3],
|
10
|
+
doc="If check limit=True the crashy_fn will crash if this value is >1",
|
11
|
+
)
|
12
|
+
return_value = bch.ResultVar(
|
13
|
+
units="ul",
|
14
|
+
doc="This is a dummy result variable. In this example, it is the same as the value passed in.",
|
15
|
+
)
|
16
|
+
trigger_crash = bch.ResultVar(
|
17
|
+
units="True/False",
|
18
|
+
doc="if true crashy_fn will crash when input_val >1",
|
19
|
+
)
|
20
|
+
|
21
|
+
def crashy_fn(self, input_val: int = 0, **kwargs) -> float: # pylint: disable=unused-argument
|
22
|
+
if self.trigger_crash:
|
23
|
+
if input_val > 1:
|
24
|
+
raise RuntimeError("I crashed for no good reason ;P")
|
25
|
+
|
26
|
+
return {"return_value": input_val, "trigger_crash": self.trigger_crash}
|
27
|
+
|
28
|
+
|
29
|
+
def example_sample_cache(
|
30
|
+
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(),
|
31
|
+
report: bch.BenchReport = bch.BenchReport(),
|
32
|
+
trigger_crash: bool = False,
|
33
|
+
) -> bch.Bench:
|
34
|
+
"""This example shows how to use the use_sample_cache option to deal with unreliable functions and to continue benchmarking using previously calculated results even if the code crashed during the run
|
35
|
+
|
36
|
+
Args:
|
37
|
+
run_cfg (BenchRunCfg): configuration of how to perform the param sweep
|
38
|
+
trigger_crash: (bool): Turn on/off code to artificially trigger a crash
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
Bench: results of the parameter sweep
|
42
|
+
"""
|
43
|
+
|
44
|
+
instance = UnreliableClass()
|
45
|
+
instance.trigger_crash = trigger_crash
|
46
|
+
|
47
|
+
bencher = bch.Bench("example_sample_cache", instance.crashy_fn, run_cfg=run_cfg, report=report)
|
48
|
+
|
49
|
+
bencher.plot_sweep(
|
50
|
+
title="Example Crashy Function with the sample_cache",
|
51
|
+
input_vars=[UnreliableClass.param.input_val],
|
52
|
+
result_vars=[UnreliableClass.param.return_value, UnreliableClass.param.trigger_crash],
|
53
|
+
description="""This example shows how to use the use_sample_cache option to deal with unreliable functions and to continue benchmarking using previously calculated results even if the code crashed during the run""",
|
54
|
+
run_cfg=run_cfg,
|
55
|
+
post_description="The input_val vs return value graph is a straight line as expected and there is no record of the fact the benchmark crashed halfway through. The second graph shows that for values >1 the trigger_crash value had to be 0 in order to proceed",
|
56
|
+
)
|
57
|
+
return bencher
|
58
|
+
|
59
|
+
|
60
|
+
if __name__ == "__main__":
|
61
|
+
ex_run_cfg = bch.BenchRunCfg()
|
62
|
+
ex_run_cfg.repeats = 1
|
63
|
+
ex_run_cfg.executor = bch.Executors.SCOOP
|
64
|
+
|
65
|
+
# this will store the result of of every call to crashy_fn
|
66
|
+
ex_run_cfg.use_sample_cache = True
|
67
|
+
ex_run_cfg.clear_sample_cache = True
|
68
|
+
|
69
|
+
try:
|
70
|
+
# this will crash after iteration 2 because we are checking the crash_threshold >1. We don't want to lose those (potentially expensive to calculate) datapoints so they are stored in the sample_cache
|
71
|
+
example_sample_cache(ex_run_cfg, trigger_crash=True)
|
72
|
+
except RuntimeError as e:
|
73
|
+
print(f"caught the exception {e}")
|
74
|
+
|
75
|
+
print(
|
76
|
+
"Running the same benchmark but without checking the limit. The benchmarking should load the previously calculated values and continue to finish calculating the values that were missed due to the crash"
|
77
|
+
)
|
78
|
+
ex_run_cfg.clear_sample_cache = False
|
79
|
+
example_sample_cache(ex_run_cfg, trigger_crash=False)
|
80
|
+
|
81
|
+
ex_run_cfg.repeats = 2
|
82
|
+
|
83
|
+
example_sample_cache(ex_run_cfg, trigger_crash=False).report.show()
|
84
|
+
|
85
|
+
# see the test_sample_cache for a more detailed explanation of the mechanisms of the cache
|
@@ -0,0 +1,116 @@
|
|
1
|
+
from enum import auto
|
2
|
+
|
3
|
+
from strenum import StrEnum
|
4
|
+
|
5
|
+
import bencher as bch
|
6
|
+
|
7
|
+
|
8
|
+
class ExampleEnum(StrEnum):
|
9
|
+
value_1 = auto()
|
10
|
+
value_2 = auto()
|
11
|
+
# value3 = auto()
|
12
|
+
# value4 = auto()
|
13
|
+
|
14
|
+
|
15
|
+
class Cfg(bch.ParametrizedSweep):
|
16
|
+
enum1 = bch.EnumSweep(ExampleEnum)
|
17
|
+
result = bch.ResultVar()
|
18
|
+
|
19
|
+
# def __call__(self,**kwargs) -> Any:
|
20
|
+
# self.update_params_from_kwargs(**kwargs)
|
21
|
+
# self.result = float(str(self.enum1)[-1])
|
22
|
+
# return self.get_results_values_as_dict()
|
23
|
+
|
24
|
+
|
25
|
+
def bench_function(cfg: Cfg):
|
26
|
+
return {"result": float(str(cfg.enum1)[-1])}
|
27
|
+
|
28
|
+
|
29
|
+
def print_assert_equal(msg, first, second):
|
30
|
+
print(f"{msg} {first}=={second}")
|
31
|
+
assert first == second
|
32
|
+
|
33
|
+
|
34
|
+
def assert_call_counts(bencher, run_cfg, wrapper_calls=-1, fn_calls=-1, cache_calls=-1):
|
35
|
+
print_assert_equal(
|
36
|
+
"worker wrapper call count",
|
37
|
+
bencher.sample_cache.worker_wrapper_call_count,
|
38
|
+
wrapper_calls * run_cfg.repeats,
|
39
|
+
)
|
40
|
+
print_assert_equal(
|
41
|
+
"worker fn call count",
|
42
|
+
bencher.sample_cache.worker_fn_call_count,
|
43
|
+
fn_calls * run_cfg.repeats,
|
44
|
+
)
|
45
|
+
print_assert_equal(
|
46
|
+
"worker cache call count",
|
47
|
+
bencher.sample_cache.worker_cache_call_count,
|
48
|
+
cache_calls * run_cfg.repeats,
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def example_cache_context() -> bch.Bench:
|
53
|
+
run_cfg = bch.BenchRunCfg()
|
54
|
+
run_cfg.use_sample_cache = True
|
55
|
+
run_cfg.only_hash_tag = True
|
56
|
+
run_cfg.repeats = 2
|
57
|
+
run_cfg.parallel = False
|
58
|
+
|
59
|
+
bencher = bch.Bench("bench_context", bench_function, Cfg, run_cfg=run_cfg)
|
60
|
+
|
61
|
+
# clear all tags from the cache at the beginning so that the example works the same not matter how many times the example is run. When using this for you own code you probably don't want to clear the cache at the beginning because you will lose all the data you collected.
|
62
|
+
bencher.clear_tag_from_sample_cache("example_tag1", run_cfg)
|
63
|
+
bencher.clear_tag_from_sample_cache("example_tag2", run_cfg)
|
64
|
+
|
65
|
+
# run a benchmark with a constant value and save results with example_tag1
|
66
|
+
bencher.plot_sweep(
|
67
|
+
title="Benchmark enum=value_1",
|
68
|
+
const_vars=[Cfg.param.enum1.with_const(ExampleEnum.value_1)],
|
69
|
+
result_vars=[Cfg.param.result],
|
70
|
+
tag="example_tag1",
|
71
|
+
)
|
72
|
+
|
73
|
+
# there are not values in the cache, so we expect 1 fn call and 0 cache calls
|
74
|
+
assert_call_counts(bencher, run_cfg, wrapper_calls=1, fn_calls=1, cache_calls=0)
|
75
|
+
|
76
|
+
# now run another benchmark with the same tag but a different value
|
77
|
+
bencher.clear_call_counts()
|
78
|
+
bencher.plot_sweep(
|
79
|
+
title="Benchmark enum=value_2",
|
80
|
+
const_vars=[Cfg.param.enum1.with_const(ExampleEnum.value_2)],
|
81
|
+
result_vars=[Cfg.param.result],
|
82
|
+
tag="example_tag1",
|
83
|
+
)
|
84
|
+
|
85
|
+
# these values have not been calcuated before so there should be 1 fn call
|
86
|
+
assert_call_counts(bencher, run_cfg, wrapper_calls=1, fn_calls=1, cache_calls=0)
|
87
|
+
|
88
|
+
# now create a new benchmark that calculates the values of the previous two benchmarks. The tag is the same so those values will be loaded from the cache instead of getting calculated again
|
89
|
+
bencher.clear_call_counts()
|
90
|
+
bencher.plot_sweep(
|
91
|
+
title="Benchmark enum=[value_1,value_2] combined",
|
92
|
+
input_vars=[Cfg.param.enum1],
|
93
|
+
result_vars=[Cfg.param.result],
|
94
|
+
tag="example_tag1",
|
95
|
+
)
|
96
|
+
|
97
|
+
# both calls hit the cache.
|
98
|
+
assert_call_counts(bencher, run_cfg, wrapper_calls=2, fn_calls=0, cache_calls=2)
|
99
|
+
|
100
|
+
# run the same benchmark as before but use a different tag. The previously cached values will not be used and fresh values will be calculated instead
|
101
|
+
bencher.clear_call_counts()
|
102
|
+
bencher.plot_sweep(
|
103
|
+
title="Benchmark enum=[value_1,value_2] with different tag",
|
104
|
+
input_vars=[Cfg.param.enum1],
|
105
|
+
result_vars=[Cfg.param.result],
|
106
|
+
tag="example_tag2",
|
107
|
+
)
|
108
|
+
|
109
|
+
# Both calls are calcuated becuase the tag is different so they don't hit the cache
|
110
|
+
assert_call_counts(bencher, run_cfg, wrapper_calls=2, fn_calls=2, cache_calls=0)
|
111
|
+
|
112
|
+
return bencher
|
113
|
+
|
114
|
+
|
115
|
+
if __name__ == "__main__":
|
116
|
+
example_cache_context().report.show()
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# you need this import to be able to reference a class from a static method in that class
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
import math
|
5
|
+
import random
|
6
|
+
import time
|
7
|
+
from datetime import datetime
|
8
|
+
from enum import auto
|
9
|
+
|
10
|
+
from strenum import StrEnum
|
11
|
+
|
12
|
+
import bencher as bch
|
13
|
+
|
14
|
+
|
15
|
+
# define a class with the output variables you want to benchmark. It must inherit from ParametrizedSweep (which inherits from param.Parametrized). Param is a python library that allows you to track metadata about parameters. I would recommend reading at least the intro: https://param.holoviz.org/. I have extended param with some extra metadata such is the units of the variable so that it can automaticaly be plotted.
|
16
|
+
class OutputCfg(bch.ParametrizedSweep):
|
17
|
+
"""A class for defining what variables the benchmark function returns and metadata on those variables"""
|
18
|
+
|
19
|
+
# Documenting the variable here with enables automatic summaries of what has been benchmarked.
|
20
|
+
# This made up example uses accuracy as an example, but the variable defined here can be any metric that is important for the performance of a system. You can also define the direction of the optimisation i.e. to minimise or maximise the metric.
|
21
|
+
accuracy = bch.ResultVar(
|
22
|
+
units="%", direction=bch.OptDir.maximize, doc="The accuracy of the algorithm."
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
# Define categorical variables with enums that inherit from StrEnum. In this example, its just an arbitrary set of categories that have an unknown influence on the metric you want to understand. In a real world case these would be a set of conditions or settings you are benchmarking
|
27
|
+
class AlgoSetting(StrEnum):
|
28
|
+
"""Use enums to describe categorical input. In this example they are given names that describe how they affect the function output, but in a real world example these will be some settings to an algorithm that you want to understand how they affect the metric you are trying to optimise."""
|
29
|
+
|
30
|
+
# add some random noise to the output. When your algorithm has noisy output it often is an indication that something is not quite right. The graphs should show that you want to avoid the "noisy" setting in your algorithm
|
31
|
+
noisy = auto()
|
32
|
+
|
33
|
+
# This is the setting with the best performance, and characterising that that is is the goal of the benchmarking
|
34
|
+
optimum = auto()
|
35
|
+
|
36
|
+
poor = auto() # this setting results in poor performance
|
37
|
+
|
38
|
+
|
39
|
+
# define a class with the input variables you want to benchmark. It must inherit from ParametrizeSweep. This class defines a struct that is passed to the benchmark function. The function must be pure and so we define it as a staticmethod that takes an InputCfg class and returns an OutputCfg class. By accepting and returning parametrized classes the metadata about what the relationship between the input and output are easy to track.
|
40
|
+
class InputCfg(bch.ParametrizedSweep):
|
41
|
+
# The variables must be defined as one of the Sweep types, i.e, FloatSweep, IntSweep, EnumSweep from bencher.bench_vars
|
42
|
+
# theta = FloatSweep(default=0, bounds=[0, math.pi], doc="Input angle", units="rad", samples=30)
|
43
|
+
|
44
|
+
# Define sweep variables by passing in an enum class name. The first element of the enum is the default by convention, but you can overrride the default in the constructor
|
45
|
+
algo_setting_enum = bch.EnumSweep(AlgoSetting, default=AlgoSetting.poor)
|
46
|
+
|
47
|
+
# In this case there are no units so its marked as unitless or ul. You can define how many evenly distributed samples to sample the parameter with
|
48
|
+
algo_setting_float = bch.FloatSweep(
|
49
|
+
default=0.0,
|
50
|
+
bounds=[0.0, 6.0],
|
51
|
+
doc="This represents a continuous input value to your function that affects the desired output in a way you want to characterise.",
|
52
|
+
units="ul",
|
53
|
+
samples=10,
|
54
|
+
)
|
55
|
+
|
56
|
+
# define the objective function you want to benchmark. It must be static and have no side effects. It should accept 1 input of type InputCfg (or whatever your input config class is called) and return the OutputCfg class you have defined
|
57
|
+
@staticmethod
|
58
|
+
def bench_function(cfg: InputCfg) -> OutputCfg:
|
59
|
+
"""Takes an ExampleBenchCfgIn and returns a ExampleBenchCfgOut output. This is just a dummy example so the behavior of the function is rather transparent, but in a real use case the function would be a black box you want to characterise."""
|
60
|
+
output = OutputCfg()
|
61
|
+
|
62
|
+
output.accuracy = 50 + math.sin(cfg.algo_setting_float) * 5
|
63
|
+
|
64
|
+
# this simulates random long term change in the function
|
65
|
+
output.accuracy += time.localtime(datetime.now().second).tm_sec / 30
|
66
|
+
|
67
|
+
match cfg.algo_setting_enum:
|
68
|
+
case AlgoSetting.noisy:
|
69
|
+
# add some random noise to the output. When your algorith has noisy output it often is an indication that something is not quite right. The graphs should show that you want to avoid the "noisy" setting in your algorithm
|
70
|
+
output.accuracy += random.uniform(-10, 10)
|
71
|
+
case AlgoSetting.optimum:
|
72
|
+
output.accuracy += 30 # This is the setting with the best performance, and characterising that is is the goal of the benchmarking
|
73
|
+
case AlgoSetting.poor:
|
74
|
+
output.accuracy -= 20 # this setting results in poor performance
|
75
|
+
return output
|
76
|
+
|
77
|
+
|
78
|
+
if __name__ == "__main__":
|
79
|
+
# pass the objective function you have defined to bencher. This benchmark function can be reused for multiple sweeps. You also need to pass the inputCfg to the bencher so that it can process the metadata about the input configuration.
|
80
|
+
bench = bch.Bench("Bencher_Example_Categorical", InputCfg.bench_function, InputCfg)
|
81
|
+
|
82
|
+
# Bencher needs to know the metadata of the variable in order to automatically sweep and plot it, so it is passed by using param's metadata syntax. InputCfg.param.* is how to access the metadata defined in the class description. Unfortunately vscode autocomplete doesn't work with params metaclass machinery so you will need to look at the class definition to get a list of possible settings. Define what parameter you want to sweep over and the result variable you want to plot. If you pass 1 input, it will perform a 1D sweep over that dimension and plot a line or a bar graph of the result (depending on if that variable on continuous or discrete). In this example we are going to sweep the enum variable and record the accuracy.
|
83
|
+
bench.plot_sweep(
|
84
|
+
input_vars=[InputCfg.param.algo_setting_enum],
|
85
|
+
result_vars=[OutputCfg.param.accuracy],
|
86
|
+
title="Simple example 1D enum sweep",
|
87
|
+
description="""Sample all the values in enum setting and record the resulting accuracy. The algo_setting_float is not mentioned in the inputs and so it takes the default value that was set in the InputCfg class. Repeats=10 so the benchmark function is called 10 times serially. This is why the function must be pure, if a past call to the function affects the future call to the function (through global side effects) any statistics you calculate will not be correct.
|
88
|
+
""",
|
89
|
+
post_description="Here you can see the affect of each setting on the output and the optimum is clearly the best.",
|
90
|
+
run_cfg=bch.BenchRunCfg(repeats=10),
|
91
|
+
)
|
92
|
+
|
93
|
+
# There is also a floating point input setting that affects the performance of the algorithm. By passing only the float setting, the InputCfg class will use the default setting of the categorical value so you can understand the float setting in isolation
|
94
|
+
bench.plot_sweep(
|
95
|
+
input_vars=[InputCfg.param.algo_setting_float],
|
96
|
+
result_vars=[OutputCfg.param.accuracy],
|
97
|
+
title="Simple example 1D float sweep",
|
98
|
+
description="""Perform a 1D sweep over the continuous variable algo_setting_float taking sweep the bounds and number of samples from the InputCfg class definition. The algo_setting_enum is not mentioned in the inputs and so it takes the default value that was set in the InputCfg class. Repeats=10 so the benchmark function is called 10 times serially.
|
99
|
+
""",
|
100
|
+
post_description="The plot shows the output is affected by the float input in a continuous way with a peak around 1.5",
|
101
|
+
run_cfg=bch.BenchRunCfg(repeats=10),
|
102
|
+
)
|
103
|
+
|
104
|
+
# This sweep is a combination of the previous two sweeps
|
105
|
+
bench.plot_sweep(
|
106
|
+
input_vars=[
|
107
|
+
InputCfg.param.algo_setting_float,
|
108
|
+
InputCfg.param.algo_setting_enum,
|
109
|
+
],
|
110
|
+
result_vars=[OutputCfg.param.accuracy],
|
111
|
+
title="Simple example 2D sweep",
|
112
|
+
description="""Perform a 2D sweep over the enum and continuous variable to see how they act together. Here the setting use_optuna=True so additional graphs a plotted at the end.
|
113
|
+
""",
|
114
|
+
post_description="In this example function the two input settings combine in a linear and predictable way, so the best combination of settings is enum = AlgoSetting.optimum and float = 1.33. Setting use_optuna=True adds a plot of how much each input parameter affects the metric and a printout of the best parameter values found during the sweep. If the value for repeat is high it is an indication there is something wrong with your benchmark function. The repeat should have no affect on the value of the function if calls to the function are independent. This can be useful to detect undesired side effects in your code",
|
115
|
+
run_cfg=bch.BenchRunCfg(repeats=10, use_optuna=True, serve_xarray=True, serve_pandas=True),
|
116
|
+
)
|
117
|
+
|
118
|
+
# In the last example we track the value of the categorical values over time.
|
119
|
+
# run this code in a loop twice to simulate calling the benchmarking function at different times. The most common use case for tracking over time would be run once a day during nightly benchmarking
|
120
|
+
bench.plot_sweep(
|
121
|
+
input_vars=[InputCfg.param.algo_setting_enum],
|
122
|
+
result_vars=[OutputCfg.param.accuracy],
|
123
|
+
const_vars=[(InputCfg.param.algo_setting_float, 1.33)],
|
124
|
+
title="Simple example 1D sweep over time",
|
125
|
+
description="""Once you have found the optimal settings for your algorithm you want to make sure that the performance is not lost over time. You can set variables to a constant value and in this case the float value is set to its optimum value. The first time this function is run only the results from sweeping the categorical value is plotted (the same as example 1), but the second time it is run a graph the values over time is shown. [Run the code again if you don't see a graph over time]. If the graphs over time shows long term changes (not just noise), it indicate there is another external factor that is affecting your performace over time, i.e. dependencies changing, physical degradation of equipment, an unnoticed bug from a pull request etc...
|
126
|
+
|
127
|
+
This shows the basic features of bencher. These examples are purposefully simplified to demonstrate its features in isolation and don't reeally show the real advantages of bencher. If you only have a few inputs and outputs its not that complicated to throw together some plots of performance. The power of bencher is that when you have a system with many moving parts that all interact with eachother, teasing apart those influences becomes much harder because the parameter spaces combine quite quickly into a high dimensional mess. Bencher makes it easier to experiment with different combination of inputs to gain an intuition of the system performance. Bencher can plot up to 6D input natively and you can add custom plots if you have exotic data types or state spaces [WIP].
|
128
|
+
""",
|
129
|
+
post_description="",
|
130
|
+
run_cfg=bch.BenchRunCfg(repeats=10, over_time=True, clear_history=False),
|
131
|
+
)
|
132
|
+
|
133
|
+
# launch web server and view
|
134
|
+
bench.report.show()
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"""This file has some examples for how to perform basic benchmarking parameter sweeps"""
|
2
|
+
|
3
|
+
import bencher as bch
|
4
|
+
|
5
|
+
# All the examples will be using the data structures and benchmark function defined in this file
|
6
|
+
from bencher.example.benchmark_data import ExampleBenchCfgIn, ExampleBenchCfgOut, bench_function
|
7
|
+
|
8
|
+
|
9
|
+
def example_1D_bool(run_cfg: bch.BenchRunCfg) -> bch.Bench:
|
10
|
+
"""This example shows how to sample a 1 dimensional categorical variable and plot the result of passing that parameter sweep to the benchmarking function"""
|
11
|
+
|
12
|
+
bench = bch.Bench(
|
13
|
+
"benchmarking_example_categorical1D",
|
14
|
+
bench_function,
|
15
|
+
ExampleBenchCfgIn,
|
16
|
+
)
|
17
|
+
|
18
|
+
# here we sample the input variable theta and plot the value of output1. The (noisy) function is sampled 20 times so you can see the distribution
|
19
|
+
res = bench.plot_sweep(
|
20
|
+
title="Example 1D Bool",
|
21
|
+
input_vars=[ExampleBenchCfgIn.param.noisy],
|
22
|
+
result_vars=[ExampleBenchCfgOut.param.out_sin],
|
23
|
+
description=example_1D_bool.__doc__,
|
24
|
+
run_cfg=run_cfg,
|
25
|
+
)
|
26
|
+
bench.report.append(res.to_bar())
|
27
|
+
|
28
|
+
return bench
|
29
|
+
|
30
|
+
|
31
|
+
if __name__ == "__main__":
|
32
|
+
ex_run_cfg = bch.BenchRunCfg()
|
33
|
+
ex_run_cfg.repeats = 20
|
34
|
+
|
35
|
+
example_1D_bool(ex_run_cfg).report.show()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""This file has some examples for how to perform basic benchmarking parameter sweeps"""
|
2
|
+
|
3
|
+
# pylint: disable=duplicate-code
|
4
|
+
|
5
|
+
import bencher as bch
|
6
|
+
|
7
|
+
# All the examples will be using the data structures and benchmark function defined in this file
|
8
|
+
from bencher.example.benchmark_data import ExampleBenchCfgIn, ExampleBenchCfgOut, bench_function
|
9
|
+
|
10
|
+
|
11
|
+
def example_1D_cat(
|
12
|
+
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
|
13
|
+
) -> bch.Bench:
|
14
|
+
"""This example shows how to sample a 1 dimensional categorical variable and plot the result of passing that parameter sweep to the benchmarking function
|
15
|
+
|
16
|
+
Args:
|
17
|
+
run_cfg (BenchRunCfg): configuration of how to perform the param sweep
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
Bench: results of the parameter sweep
|
21
|
+
"""
|
22
|
+
|
23
|
+
explorer = ExampleBenchCfgIn()
|
24
|
+
bench = bch.Bench(
|
25
|
+
"benchmarking_example_categorical1D",
|
26
|
+
bench_function,
|
27
|
+
ExampleBenchCfgIn,
|
28
|
+
run_cfg=run_cfg,
|
29
|
+
report=report,
|
30
|
+
)
|
31
|
+
|
32
|
+
# here we sample the input variable theta and plot the value of output1. The (noisy) function is sampled 20 times so you can see the distribution
|
33
|
+
bench.plot_sweep(
|
34
|
+
title="Example 1D Categorical",
|
35
|
+
description="""This example shows how to sample a 1 dimensional categorical variable and plot the result of passing that parameter sweep to the benchmarking function""",
|
36
|
+
input_vars=[ExampleBenchCfgIn.param.postprocess_fn],
|
37
|
+
result_vars=[ExampleBenchCfgOut.param.out_cos, ExampleBenchCfgOut.param.out_sin],
|
38
|
+
const_vars=explorer.get_input_defaults(),
|
39
|
+
)
|
40
|
+
|
41
|
+
return bench
|
42
|
+
|
43
|
+
|
44
|
+
if __name__ == "__main__":
|
45
|
+
ex_run_cfg = bch.BenchRunCfg()
|
46
|
+
ex_run_cfg.repeats = 10
|
47
|
+
|
48
|
+
srv1 = example_1D_cat(ex_run_cfg).report.show()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""This file has some examples for how to perform basic benchmarking parameter sweeps"""
|
2
|
+
|
3
|
+
import math
|
4
|
+
import bencher as bch
|
5
|
+
|
6
|
+
|
7
|
+
class SimpleFloat(bch.ParametrizedSweep):
|
8
|
+
theta = bch.FloatSweep(
|
9
|
+
default=0, bounds=[0, math.pi], doc="Input angle", units="rad", samples=30
|
10
|
+
)
|
11
|
+
out_sin = bch.ResultVar(units="v", doc="sin of theta")
|
12
|
+
|
13
|
+
def __call__(self, **kwargs):
|
14
|
+
self.update_params_from_kwargs(**kwargs)
|
15
|
+
self.out_sin = math.sin(self.theta)
|
16
|
+
return super().__call__(**kwargs)
|
17
|
+
|
18
|
+
|
19
|
+
def example_1D_float(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None) -> bch.Bench:
|
20
|
+
"""This example shows how to sample a 1 dimensional float variable and plot the result of passing that parameter sweep to the benchmarking function"""
|
21
|
+
|
22
|
+
bench = SimpleFloat().to_bench(run_cfg, report)
|
23
|
+
bench.plot_sweep()
|
24
|
+
return bench
|
25
|
+
|
26
|
+
|
27
|
+
if __name__ == "__main__":
|
28
|
+
example_1D_float().report.show()
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""This file contains examples for how to perform basic 2D benchmarking parameter sweeps"""
|
2
|
+
|
3
|
+
import math
|
4
|
+
import bencher as bch
|
5
|
+
|
6
|
+
|
7
|
+
class SimpleFloat(bch.ParametrizedSweep):
|
8
|
+
theta = bch.FloatSweep(
|
9
|
+
default=0, bounds=[0, math.pi], doc="Input angle", units="rad", samples=30
|
10
|
+
)
|
11
|
+
offset = bch.FloatSweep(default=0, bounds=[0, 1], doc="Input angle", units="rad")
|
12
|
+
out_sin = bch.ResultVar(units="v", doc="sin of theta")
|
13
|
+
|
14
|
+
def __call__(self, **kwargs):
|
15
|
+
self.update_params_from_kwargs(**kwargs)
|
16
|
+
self.out_sin = math.sin(self.theta) + self.offset
|
17
|
+
return super().__call__(**kwargs)
|
18
|
+
|
19
|
+
|
20
|
+
def example_2D_float(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None) -> bch.Bench:
|
21
|
+
"""This example shows how to sample a 1 dimensional float variable and plot the result of passing that parameter sweep to the benchmarking function"""
|
22
|
+
|
23
|
+
bench = SimpleFloat().to_bench(run_cfg, report)
|
24
|
+
bench.plot_sweep()
|
25
|
+
return bench
|
26
|
+
|
27
|
+
|
28
|
+
if __name__ == "__main__":
|
29
|
+
example_2D_float().report.show()
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import bencher as bch
|
2
|
+
|
3
|
+
|
4
|
+
class TestPrinting(bch.ParametrizedSweep):
|
5
|
+
# INPUTS
|
6
|
+
a = bch.StringSweep(default=None, string_list=["a1", "a2"], allow_None=True)
|
7
|
+
b = bch.StringSweep(default=None, string_list=["b1", "b2"], allow_None=True)
|
8
|
+
c = bch.StringSweep(default=None, string_list=["c1", "c2"], allow_None=True)
|
9
|
+
d = bch.StringSweep(default=None, string_list=["d1", "d2"], allow_None=True)
|
10
|
+
|
11
|
+
# RESULTS
|
12
|
+
result = bch.ResultString()
|
13
|
+
|
14
|
+
def __call__(self, **kwargs):
|
15
|
+
self.update_params_from_kwargs(**kwargs)
|
16
|
+
|
17
|
+
self.result = self.a
|
18
|
+
if self.b is not None:
|
19
|
+
self.result += f",{self.b}"
|
20
|
+
if self.c is not None:
|
21
|
+
self.result += f",{self.c}"
|
22
|
+
if self.d is not None:
|
23
|
+
self.result += f",{self.d}"
|
24
|
+
self.result += "\n\ttab\n\t\ttab2"
|
25
|
+
|
26
|
+
self.result = bch.tabs_in_markdown(self.result)
|
27
|
+
return super().__call__()
|
28
|
+
|
29
|
+
|
30
|
+
def example_strings(
|
31
|
+
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
|
32
|
+
) -> bch.Bench:
|
33
|
+
bench = bch.Bench("strings", TestPrinting(), run_cfg=run_cfg, report=report)
|
34
|
+
|
35
|
+
for s in [
|
36
|
+
[TestPrinting.param.a],
|
37
|
+
[TestPrinting.param.a, TestPrinting.param.b],
|
38
|
+
[TestPrinting.param.a, TestPrinting.param.b, TestPrinting.param.c],
|
39
|
+
[TestPrinting.param.a, TestPrinting.param.b, TestPrinting.param.c, TestPrinting.param.d],
|
40
|
+
]:
|
41
|
+
bench.plot_sweep(f"String Panes {[v.name for v in s]}", input_vars=s)
|
42
|
+
|
43
|
+
return bench
|
44
|
+
|
45
|
+
|
46
|
+
if __name__ == "__main__":
|
47
|
+
example_strings().report.show()
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"""This file has some examples for how to perform basic benchmarking parameter sweeps"""
|
2
|
+
|
3
|
+
# pylint: disable=duplicate-code
|
4
|
+
|
5
|
+
import bencher as bch
|
6
|
+
|
7
|
+
# All the examples will be using the data structures and benchmark function defined in this file
|
8
|
+
from bencher.example.benchmark_data import ExampleBenchCfgIn, ExampleBenchCfgOut, bench_function
|
9
|
+
|
10
|
+
|
11
|
+
def example_time_event(
|
12
|
+
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
|
13
|
+
) -> bch.Bench:
|
14
|
+
"""This example shows how to manually set time events as a string so that progress can be monitored over time"""
|
15
|
+
|
16
|
+
bencher = bch.Bench(
|
17
|
+
"benchmarking_example_categorical1D",
|
18
|
+
bench_function,
|
19
|
+
ExampleBenchCfgIn,
|
20
|
+
run_cfg=run_cfg,
|
21
|
+
report=report,
|
22
|
+
)
|
23
|
+
|
24
|
+
ExampleBenchCfgIn.param.offset.bounds = [0, 100]
|
25
|
+
|
26
|
+
# manually override the default value based on the time event string so that the graphs are not all just straight lines
|
27
|
+
ExampleBenchCfgIn.param.offset.default = int(str(hash(run_cfg.time_event))[-1])
|
28
|
+
|
29
|
+
# here we sample the input variable theta and plot the value of output1. The (noisy) function is sampled 20 times so you can see the distribution
|
30
|
+
bencher.plot_sweep(
|
31
|
+
title="Example 1D Categorical",
|
32
|
+
input_vars=[ExampleBenchCfgIn.param.postprocess_fn],
|
33
|
+
result_vars=[ExampleBenchCfgOut.param.out_cos],
|
34
|
+
description=example_time_event.__doc__,
|
35
|
+
run_cfg=run_cfg,
|
36
|
+
)
|
37
|
+
return bencher
|
38
|
+
|
39
|
+
|
40
|
+
def run_example_time_event(ex_run_cfg):
|
41
|
+
ex_run_cfg.repeats = 1
|
42
|
+
ex_run_cfg.print_pandas = True
|
43
|
+
ex_run_cfg.over_time = True
|
44
|
+
|
45
|
+
ex_run_cfg.clear_cache = True
|
46
|
+
ex_run_cfg.clear_history = True
|
47
|
+
|
48
|
+
ex_run_cfg.time_event = "-first_event"
|
49
|
+
example_time_event(ex_run_cfg)
|
50
|
+
|
51
|
+
ex_run_cfg.clear_cache = False
|
52
|
+
ex_run_cfg.clear_history = False
|
53
|
+
ex_run_cfg.time_event = "_second_event"
|
54
|
+
example_time_event(ex_run_cfg)
|
55
|
+
|
56
|
+
ex_run_cfg.time_event = (
|
57
|
+
"*third_event has a very very long label to demonstrate automatic text wrapping"
|
58
|
+
)
|
59
|
+
return example_time_event(ex_run_cfg)
|
60
|
+
|
61
|
+
|
62
|
+
if __name__ == "__main__":
|
63
|
+
run_example_time_event(bch.BenchRunCfg()).report.show()
|