holobench 1.40.1__py3-none-any.whl → 1.42.0__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.
- CHANGELOG.md +10 -0
- bencher/__init__.py +20 -2
- bencher/bench_cfg.py +265 -61
- bencher/bench_report.py +2 -2
- bencher/bench_runner.py +96 -10
- bencher/bencher.py +421 -89
- bencher/caching.py +1 -4
- bencher/class_enum.py +70 -7
- bencher/example/example_composable_container_image.py +60 -0
- bencher/example/example_composable_container_video.py +49 -0
- bencher/example/example_dataframe.py +2 -2
- bencher/example/example_image.py +17 -21
- bencher/example/example_image1.py +16 -20
- bencher/example/example_levels.py +17 -173
- bencher/example/example_pareto.py +107 -31
- bencher/example/example_rerun2.py +1 -1
- bencher/example/example_simple_bool.py +2 -2
- bencher/example/example_simple_float2d.py +6 -1
- bencher/example/example_video.py +35 -17
- bencher/example/experimental/example_hvplot_explorer.py +3 -4
- bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
- bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
- bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
- bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
- bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
- bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
- bencher/example/inputs_1D/example1d_common.py +48 -12
- bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
- bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
- bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +15 -0
- bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
- bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
- bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
- bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
- bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
- bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
- bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
- bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
- bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
- bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
- bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
- bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
- bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
- bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
- bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
- bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
- bencher/example/meta/generate_examples.py +124 -7
- bencher/example/meta/generate_meta.py +88 -40
- bencher/job.py +175 -12
- bencher/plotting/plot_filter.py +52 -17
- bencher/results/bench_result.py +119 -26
- bencher/results/bench_result_base.py +119 -10
- bencher/results/composable_container/composable_container_video.py +39 -12
- bencher/results/dataset_result.py +6 -200
- bencher/results/explorer_result.py +23 -0
- bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
- bencher/results/holoview_results/__init__.py +0 -0
- bencher/results/holoview_results/bar_result.py +79 -0
- bencher/results/holoview_results/curve_result.py +110 -0
- bencher/results/holoview_results/distribution_result/__init__.py +0 -0
- bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
- bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
- bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
- bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
- bencher/results/holoview_results/heatmap_result.py +319 -0
- bencher/results/holoview_results/holoview_result.py +346 -0
- bencher/results/holoview_results/line_result.py +240 -0
- bencher/results/holoview_results/scatter_result.py +107 -0
- bencher/results/holoview_results/surface_result.py +158 -0
- bencher/results/holoview_results/table_result.py +14 -0
- bencher/results/holoview_results/tabulator_result.py +20 -0
- bencher/results/laxtex_result.py +42 -35
- bencher/results/optuna_result.py +30 -115
- bencher/results/video_controls.py +38 -0
- bencher/results/video_result.py +39 -36
- bencher/results/video_summary.py +2 -2
- bencher/results/{plotly_result.py → volume_result.py} +29 -8
- bencher/utils.py +176 -30
- bencher/variables/inputs.py +122 -15
- bencher/video_writer.py +38 -2
- bencher/worker_job.py +34 -7
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/METADATA +21 -25
- holobench-1.42.0.dist-info/RECORD +147 -0
- bencher/example/example_composable_container.py +0 -106
- bencher/example/example_levels2.py +0 -37
- bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
- bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
- bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
- bencher/results/holoview_result.py +0 -787
- bencher/results/panel_result.py +0 -41
- holobench-1.40.1.dist-info/RECORD +0 -111
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/WHEEL +0 -0
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,22 +9,68 @@ from bencher.example.meta.example_meta import BenchableObject
|
|
9
9
|
class BenchMetaGen(bch.ParametrizedSweep):
|
10
10
|
"""This class uses bencher to display the multidimensional types bencher can represent"""
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# Benchable object to use
|
13
|
+
benchable_obj = None # Will store the benchable object instance
|
14
|
+
|
15
|
+
# Variables for controlling the sweep
|
16
|
+
float_vars_count = bch.IntSweep(
|
17
|
+
default=0, bounds=(0, 3), doc="The number of floating point variables that are swept"
|
14
18
|
)
|
15
|
-
|
16
|
-
default=0, bounds=(0,
|
19
|
+
categorical_vars_count = bch.IntSweep(
|
20
|
+
default=0, bounds=(0, 2), doc="The number of categorical variables that are swept"
|
17
21
|
)
|
18
|
-
sample_with_repeats = bch.IntSweep(default=1, bounds=(1, 100))
|
19
22
|
|
20
|
-
|
23
|
+
# Lists to store the actual variable names
|
24
|
+
float_var_names = [] # Will store float variable names
|
25
|
+
categorical_var_names = [] # Will store categorical variable names
|
26
|
+
result_var_names = [] # Will store result variable names
|
21
27
|
|
28
|
+
# Configuration options
|
29
|
+
sample_with_repeats = bch.IntSweep(default=1, bounds=(1, 100))
|
30
|
+
sample_over_time = bch.BoolSweep(default=False)
|
22
31
|
level = bch.IntSweep(default=2, units="level", bounds=(2, 5))
|
23
|
-
|
24
32
|
run_bench = False
|
25
33
|
|
26
34
|
plots = bch.ResultReference(units="int")
|
27
35
|
|
36
|
+
def __init__(
|
37
|
+
self, benchable_obj=None, float_vars=None, categorical_vars=None, result_vars=None, **params
|
38
|
+
):
|
39
|
+
"""Initialize with a benchable object and variable specifications
|
40
|
+
|
41
|
+
Args:
|
42
|
+
benchable_obj: The benchable object to use
|
43
|
+
float_vars: List of float variable names to sweep
|
44
|
+
categorical_vars: List of categorical variable names to sweep
|
45
|
+
result_vars: List of result variable names to plot
|
46
|
+
**params: Additional parameters
|
47
|
+
"""
|
48
|
+
super().__init__(**params)
|
49
|
+
|
50
|
+
# Set the benchable object
|
51
|
+
self.benchable_obj = benchable_obj if benchable_obj is not None else BenchableObject()
|
52
|
+
|
53
|
+
# Dynamically discover parameters from the benchable object
|
54
|
+
float_param_names = []
|
55
|
+
categorical_param_names = []
|
56
|
+
result_param_names = []
|
57
|
+
|
58
|
+
# Check all parameters of the benchable object
|
59
|
+
for name, param in self.benchable_obj.__class__.param.objects().items():
|
60
|
+
if hasattr(param, "bounds") and isinstance(param, bch.FloatSweep):
|
61
|
+
float_param_names.append(name)
|
62
|
+
elif isinstance(param, (bch.BoolSweep, bch.EnumSweep, bch.StringSweep)):
|
63
|
+
categorical_param_names.append(name)
|
64
|
+
elif isinstance(param, bch.ResultVar):
|
65
|
+
result_param_names.append(name)
|
66
|
+
|
67
|
+
# Use provided parameter lists or discovered ones
|
68
|
+
self.float_var_names = float_vars if float_vars is not None else float_param_names
|
69
|
+
self.categorical_var_names = (
|
70
|
+
categorical_vars if categorical_vars is not None else categorical_param_names
|
71
|
+
)
|
72
|
+
self.result_var_names = result_vars if result_vars is not None else result_param_names
|
73
|
+
|
28
74
|
def __call__(self, **kwargs: Any) -> Any:
|
29
75
|
self.update_params_from_kwargs(**kwargs)
|
30
76
|
|
@@ -34,50 +80,52 @@ class BenchMetaGen(bch.ParametrizedSweep):
|
|
34
80
|
run_cfg.over_time = self.sample_over_time
|
35
81
|
run_cfg.plot_size = 500
|
36
82
|
|
37
|
-
#
|
83
|
+
# Limit the variables to the specified counts
|
84
|
+
float_vars = []
|
85
|
+
# Apply the max_level=3 limit to the 3rd and subsequent float variables
|
86
|
+
for i, var_name in enumerate(self.float_var_names[: self.float_vars_count]):
|
87
|
+
if i >= 2: # Third variable (index 2) and beyond
|
88
|
+
float_vars.append(bch.p(var_name, max_level=3))
|
89
|
+
else:
|
90
|
+
float_vars.append(var_name)
|
38
91
|
|
39
|
-
|
40
|
-
|
41
|
-
"float2",
|
42
|
-
bch.p("float3", max_level=3),
|
43
|
-
"sigma",
|
44
|
-
]
|
92
|
+
categorical_vars = self.categorical_var_names[: self.categorical_vars_count]
|
93
|
+
input_vars = float_vars + categorical_vars
|
45
94
|
|
46
|
-
|
47
|
-
|
48
|
-
"noise_distribution",
|
49
|
-
"negate_output",
|
50
|
-
]
|
51
|
-
|
52
|
-
input_vars = inputs_vars_float[: self.float_vars] + inputs_vars_cat[: self.categorical_vars]
|
53
|
-
|
54
|
-
if self.run_bench:
|
55
|
-
bench = BenchableObject().to_bench(run_cfg)
|
95
|
+
if self.run_bench and input_vars and self.result_var_names:
|
96
|
+
bench = self.benchable_obj.to_bench(run_cfg)
|
56
97
|
res = bench.plot_sweep(
|
57
98
|
"test",
|
58
99
|
input_vars=input_vars,
|
59
|
-
result_vars=
|
100
|
+
result_vars=self.result_var_names,
|
60
101
|
plot_callbacks=False,
|
61
102
|
)
|
62
103
|
self.plots = bch.ResultReference()
|
63
104
|
self.plots.obj = res.to_auto()
|
64
105
|
|
65
|
-
title = f"{self.
|
106
|
+
title = f"{self.float_vars_count}_float_{self.categorical_vars_count}_cat"
|
66
107
|
|
67
108
|
nb = nbf.v4.new_notebook()
|
68
109
|
text = f"""# {title}"""
|
69
110
|
|
111
|
+
# Customize notebook generation to use the actual benchmark object and variables
|
112
|
+
module_import = "import bencher as bch\n"
|
113
|
+
if self.benchable_obj.__class__.__module__ != "__main__":
|
114
|
+
benchmark_import = f"from {self.benchable_obj.__class__.__module__} import {self.benchable_obj.__class__.__name__}\n"
|
115
|
+
else:
|
116
|
+
# If it's from main, use BenchableObject as fallback
|
117
|
+
benchmark_import = "from bencher.example.meta.example_meta import BenchableObject\n"
|
118
|
+
|
70
119
|
code_gen = f"""
|
71
120
|
%%capture
|
72
|
-
|
73
|
-
|
74
|
-
|
121
|
+
{module_import}
|
122
|
+
{benchmark_import}
|
75
123
|
run_cfg = bch.BenchRunCfg()
|
76
124
|
run_cfg.repeats = {self.sample_with_repeats}
|
77
125
|
run_cfg.level = 4
|
78
|
-
bench =
|
126
|
+
bench = {self.benchable_obj.__class__.__name__}().to_bench(run_cfg)
|
79
127
|
res=bench.plot_sweep(input_vars={input_vars},
|
80
|
-
result_vars=
|
128
|
+
result_vars={self.result_var_names})
|
81
129
|
"""
|
82
130
|
code_results = """
|
83
131
|
from bokeh.io import output_notebook
|
@@ -107,14 +155,14 @@ def example_meta(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None
|
|
107
155
|
description="""## All Combinations of Variable Sweeps and Resulting Plots
|
108
156
|
This uses bencher to display all the combinations of plots bencher is able to produce""",
|
109
157
|
input_vars=[
|
110
|
-
bch.p("
|
111
|
-
"
|
158
|
+
bch.p("float_vars_count", [0, 1]),
|
159
|
+
"categorical_vars_count",
|
112
160
|
bch.p("sample_with_repeats", [1, 20]),
|
113
161
|
],
|
114
162
|
const_vars=[
|
115
|
-
# BenchMeta.param.
|
163
|
+
# BenchMeta.param.float_vars_count.with_const(1),
|
116
164
|
# BenchMeta.param.sample_with_repeats.with_const(2),
|
117
|
-
# BenchMeta.param.
|
165
|
+
# BenchMeta.param.categorical_vars_count.with_const(2),
|
118
166
|
# BenchMeta.param.sample_over_time.with_const(True),
|
119
167
|
],
|
120
168
|
)
|
@@ -123,8 +171,8 @@ This uses bencher to display all the combinations of plots bencher is able to pr
|
|
123
171
|
description="""## All Combinations of Variable Sweeps and Resulting Plots
|
124
172
|
This uses bencher to display all the combinations of plots bencher is able to produce""",
|
125
173
|
input_vars=[
|
126
|
-
bch.p("
|
127
|
-
"
|
174
|
+
bch.p("float_vars_count", [2, 3]),
|
175
|
+
"categorical_vars_count",
|
128
176
|
],
|
129
177
|
)
|
130
178
|
|
@@ -133,13 +181,13 @@ This uses bencher to display all the combinations of plots bencher is able to pr
|
|
133
181
|
# description="""## All Combinations of Variable Sweeps and Resulting Plots
|
134
182
|
# This uses bencher to display all the combinations of plots bencher is able to produce""",
|
135
183
|
# input_vars=[
|
136
|
-
# bch.p("
|
137
|
-
# "
|
184
|
+
# bch.p("float_vars_count", [2, 3]),
|
185
|
+
# "categorical_vars_count",
|
138
186
|
# ],
|
139
187
|
# const_vars=[
|
140
188
|
# dict(level=3)
|
141
189
|
# # BenchMeta.param.sample_with_repeats.with_const(2),
|
142
|
-
# # BenchMeta.param.
|
190
|
+
# # BenchMeta.param.categorical_vars_count.with_const(2),
|
143
191
|
# # BenchMeta.param.sample_over_time.with_const(True),
|
144
192
|
# ],
|
145
193
|
# )
|
bencher/job.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Callable
|
3
|
-
from sortedcontainers import SortedDict
|
4
3
|
import logging
|
5
4
|
from diskcache import Cache
|
6
5
|
from concurrent.futures import Future, ProcessPoolExecutor
|
@@ -11,27 +10,72 @@ from enum import auto
|
|
11
10
|
try:
|
12
11
|
from scoop import futures as scoop_future_executor
|
13
12
|
except ImportError as e:
|
14
|
-
logging.warning(e.msg)
|
15
13
|
scoop_future_executor = None
|
16
14
|
|
17
15
|
|
18
16
|
class Job:
|
17
|
+
"""Represents a benchmarking job to be executed or retrieved from cache.
|
18
|
+
|
19
|
+
A Job encapsulates a function, its arguments, and metadata for caching
|
20
|
+
and tracking purposes.
|
21
|
+
|
22
|
+
Attributes:
|
23
|
+
job_id (str): A unique identifier for the job, used for logging
|
24
|
+
function (Callable): The function to be executed
|
25
|
+
job_args (dict): Arguments to pass to the function
|
26
|
+
job_key (str): A hash key for caching, derived from job_args if not provided
|
27
|
+
tag (str): Optional tag for grouping related jobs
|
28
|
+
"""
|
29
|
+
|
19
30
|
def __init__(
|
20
|
-
self, job_id: str, function: Callable, job_args: dict, job_key=None, tag=""
|
31
|
+
self, job_id: str, function: Callable, job_args: dict, job_key: str = None, tag: str = ""
|
21
32
|
) -> None:
|
33
|
+
"""Initialize a Job with function and arguments.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
job_id (str): A unique identifier for this job
|
37
|
+
function (Callable): The function to execute
|
38
|
+
job_args (dict): Arguments to pass to the function
|
39
|
+
job_key (str, optional): Cache key for this job. If None, will be generated
|
40
|
+
from job_args. Defaults to None.
|
41
|
+
tag (str, optional): Tag for grouping related jobs. Defaults to "".
|
42
|
+
"""
|
22
43
|
self.job_id = job_id
|
23
44
|
self.function = function
|
24
45
|
self.job_args = job_args
|
25
46
|
if job_key is None:
|
26
|
-
self.job_key = hash_sha1(tuple(
|
47
|
+
self.job_key = hash_sha1(tuple(sorted(self.job_args.items())))
|
27
48
|
else:
|
28
49
|
self.job_key = job_key
|
29
50
|
self.tag = tag
|
30
51
|
|
31
52
|
|
32
|
-
# @dataclass
|
33
53
|
class JobFuture:
|
54
|
+
"""A wrapper for a job result or future that handles caching.
|
55
|
+
|
56
|
+
This class provides a unified interface for handling both immediate results
|
57
|
+
and futures (for asynchronous execution). It also handles caching results
|
58
|
+
when they become available.
|
59
|
+
|
60
|
+
Attributes:
|
61
|
+
job (Job): The job this future corresponds to
|
62
|
+
res (dict): The result, if available immediately
|
63
|
+
future (Future): The future representing the pending job, if executed asynchronously
|
64
|
+
cache: The cache to store results in when they become available
|
65
|
+
"""
|
66
|
+
|
34
67
|
def __init__(self, job: Job, res: dict = None, future: Future = None, cache=None) -> None:
|
68
|
+
"""Initialize a JobFuture with either an immediate result or a future.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
job (Job): The job this future corresponds to
|
72
|
+
res (dict, optional): The immediate result, if available. Defaults to None.
|
73
|
+
future (Future, optional): The future representing the pending result. Defaults to None.
|
74
|
+
cache (Cache, optional): The cache to store results in. Defaults to None.
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
AssertionError: If neither res nor future is provided
|
78
|
+
"""
|
35
79
|
self.job = job
|
36
80
|
self.res = res
|
37
81
|
self.future = future
|
@@ -42,7 +86,16 @@ class JobFuture:
|
|
42
86
|
|
43
87
|
self.cache = cache
|
44
88
|
|
45
|
-
def result(self):
|
89
|
+
def result(self) -> dict:
|
90
|
+
"""Get the job result, waiting for completion if necessary.
|
91
|
+
|
92
|
+
If the result is not immediately available (i.e., it's a future),
|
93
|
+
this method will wait for the future to complete. Once the result
|
94
|
+
is available, it will be cached if a cache is provided.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
dict: The job result
|
98
|
+
"""
|
46
99
|
if self.future is not None:
|
47
100
|
self.res = self.future.result()
|
48
101
|
if self.cache is not None and self.res is not None:
|
@@ -51,18 +104,42 @@ class JobFuture:
|
|
51
104
|
|
52
105
|
|
53
106
|
def run_job(job: Job) -> dict:
|
107
|
+
"""Execute a job by calling its function with the provided arguments.
|
108
|
+
|
109
|
+
This is a helper function used primarily by executors to run jobs.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
job (Job): The job to execute
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
dict: The result of the job execution
|
116
|
+
"""
|
54
117
|
result = job.function(**job.job_args)
|
55
118
|
return result
|
56
119
|
|
57
120
|
|
58
121
|
class Executors(StrEnum):
|
122
|
+
"""Enumeration of available execution strategies for benchmark jobs.
|
123
|
+
|
124
|
+
This enum defines the execution modes for running benchmark jobs
|
125
|
+
and provides a factory method to create appropriate executors.
|
126
|
+
"""
|
127
|
+
|
59
128
|
SERIAL = auto() # slow but reliable
|
60
129
|
MULTIPROCESSING = auto() # breaks for large number of futures
|
61
130
|
SCOOP = auto() # requires running with python -m scoop your_file.py
|
62
131
|
# THREADS=auto() #not that useful as most bench code is cpu bound
|
63
132
|
|
64
133
|
@staticmethod
|
65
|
-
def factory(provider: Executors) -> Future:
|
134
|
+
def factory(provider: "Executors") -> Future | None:
|
135
|
+
"""Create an executor instance based on the specified execution strategy.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
provider (Executors): The type of executor to create
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
Future | None: The executor instance, or None for serial execution
|
142
|
+
"""
|
66
143
|
providers = {
|
67
144
|
Executors.SERIAL: None,
|
68
145
|
Executors.MULTIPROCESSING: ProcessPoolExecutor(),
|
@@ -72,7 +149,23 @@ class Executors(StrEnum):
|
|
72
149
|
|
73
150
|
|
74
151
|
class FutureCache:
|
75
|
-
"""
|
152
|
+
"""A cache system for benchmark job results with executor support.
|
153
|
+
|
154
|
+
This class provides a unified interface for running benchmark jobs either serially
|
155
|
+
or in parallel, with optional caching of results. It manages the execution strategy,
|
156
|
+
caching policy, and tracks statistics about job execution.
|
157
|
+
|
158
|
+
Attributes:
|
159
|
+
executor_type (Executors): The execution strategy to use
|
160
|
+
executor: The executor instance, created on demand
|
161
|
+
cache (Cache): Cache for storing job results
|
162
|
+
overwrite (bool): Whether to overwrite existing cached results
|
163
|
+
call_count (int): Counter for job calls
|
164
|
+
size_limit (int): Maximum size of the cache in bytes
|
165
|
+
worker_wrapper_call_count (int): Number of job submissions
|
166
|
+
worker_fn_call_count (int): Number of actual function executions
|
167
|
+
worker_cache_call_count (int): Number of cache hits
|
168
|
+
"""
|
76
169
|
|
77
170
|
def __init__(
|
78
171
|
self,
|
@@ -81,8 +174,18 @@ class FutureCache:
|
|
81
174
|
cache_name: str = "fcache",
|
82
175
|
tag_index: bool = True,
|
83
176
|
size_limit: int = int(20e9), # 20 GB
|
84
|
-
cache_results=True,
|
177
|
+
cache_results: bool = True,
|
85
178
|
):
|
179
|
+
"""Initialize a FutureCache with optional caching and execution settings.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
executor (Executors, optional): The execution strategy to use. Defaults to Executors.SERIAL.
|
183
|
+
overwrite (bool, optional): Whether to overwrite existing cached results. Defaults to True.
|
184
|
+
cache_name (str, optional): Base name for the cache directory. Defaults to "fcache".
|
185
|
+
tag_index (bool, optional): Whether to enable tag-based indexing in the cache. Defaults to True.
|
186
|
+
size_limit (int, optional): Maximum size of the cache in bytes. Defaults to 20GB.
|
187
|
+
cache_results (bool, optional): Whether to cache results at all. Defaults to True.
|
188
|
+
"""
|
86
189
|
self.executor_type = executor
|
87
190
|
self.executor = None
|
88
191
|
if cache_results:
|
@@ -100,6 +203,18 @@ class FutureCache:
|
|
100
203
|
self.worker_cache_call_count = 0
|
101
204
|
|
102
205
|
def submit(self, job: Job) -> JobFuture:
|
206
|
+
"""Submit a job for execution, with caching if enabled.
|
207
|
+
|
208
|
+
This method first checks if the job result is already in the cache (if caching is enabled
|
209
|
+
and overwrite is False). If not found in the cache, it executes the job either serially
|
210
|
+
or using the configured executor.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
job (Job): The job to submit
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
JobFuture: A future representing the job execution
|
217
|
+
"""
|
103
218
|
self.worker_wrapper_call_count += 1
|
104
219
|
|
105
220
|
if self.cache is not None:
|
@@ -132,25 +247,38 @@ class FutureCache:
|
|
132
247
|
)
|
133
248
|
|
134
249
|
def overwrite_msg(self, job: Job, suffix: str) -> None:
|
250
|
+
"""Log a message about overwriting or using cache.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
job (Job): The job being executed
|
254
|
+
suffix (str): Additional text to add to the log message
|
255
|
+
"""
|
135
256
|
msg = "OVERWRITING" if self.overwrite else "NOT in"
|
136
257
|
logging.info(f"{job.job_id} {msg} cache{suffix}")
|
137
258
|
|
138
259
|
def clear_call_counts(self) -> None:
|
139
|
-
"""Clear the worker and cache call counts, to help debug and assert caching is happening properly"""
|
260
|
+
"""Clear the worker and cache call counts, to help debug and assert caching is happening properly."""
|
140
261
|
self.worker_wrapper_call_count = 0
|
141
262
|
self.worker_fn_call_count = 0
|
142
263
|
self.worker_cache_call_count = 0
|
143
264
|
|
144
265
|
def clear_cache(self) -> None:
|
266
|
+
"""Clear all entries from the cache."""
|
145
267
|
if self.cache:
|
146
268
|
self.cache.clear()
|
147
269
|
|
148
270
|
def clear_tag(self, tag: str) -> None:
|
271
|
+
"""Remove all cache entries with the specified tag.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
tag (str): The tag identifying entries to remove from the cache
|
275
|
+
"""
|
149
276
|
logging.info(f"clearing the sample cache for tag: {tag}")
|
150
277
|
removed_vals = self.cache.evict(tag)
|
151
278
|
logging.info(f"removed: {removed_vals} items from the cache")
|
152
279
|
|
153
280
|
def close(self) -> None:
|
281
|
+
"""Close the cache and shutdown the executor if they exist."""
|
154
282
|
if self.cache:
|
155
283
|
self.cache.close()
|
156
284
|
if self.executor:
|
@@ -158,6 +286,11 @@ class FutureCache:
|
|
158
286
|
self.executor = None
|
159
287
|
|
160
288
|
def stats(self) -> str:
|
289
|
+
"""Get statistics about cache usage.
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
str: A string with cache size information
|
293
|
+
"""
|
161
294
|
logging.info(f"job calls: {self.worker_wrapper_call_count}")
|
162
295
|
logging.info(f"cache calls: {self.worker_cache_call_count}")
|
163
296
|
logging.info(f"worker calls: {self.worker_fn_call_count}")
|
@@ -167,15 +300,35 @@ class FutureCache:
|
|
167
300
|
|
168
301
|
|
169
302
|
class JobFunctionCache(FutureCache):
|
303
|
+
"""A specialized cache for a specific function with various input parameters.
|
304
|
+
|
305
|
+
This class simplifies caching results for a specific function called with
|
306
|
+
different sets of parameters. It wraps the general FutureCache with a focus
|
307
|
+
on a single function.
|
308
|
+
|
309
|
+
Attributes:
|
310
|
+
function (Callable): The function to cache results for
|
311
|
+
"""
|
312
|
+
|
170
313
|
def __init__(
|
171
314
|
self,
|
172
315
|
function: Callable,
|
173
|
-
overwrite=False,
|
174
|
-
executor:
|
316
|
+
overwrite: bool = False,
|
317
|
+
executor: Executors = Executors.SERIAL,
|
175
318
|
cache_name: str = "fcache",
|
176
319
|
tag_index: bool = True,
|
177
320
|
size_limit: int = int(100e8),
|
178
321
|
):
|
322
|
+
"""Initialize a JobFunctionCache for a specific function.
|
323
|
+
|
324
|
+
Args:
|
325
|
+
function (Callable): The function to cache results for
|
326
|
+
overwrite (bool, optional): Whether to overwrite existing cached results. Defaults to False.
|
327
|
+
executor (Executors, optional): The execution strategy to use. Defaults to Executors.SERIAL.
|
328
|
+
cache_name (str, optional): Base name for the cache directory. Defaults to "fcache".
|
329
|
+
tag_index (bool, optional): Whether to enable tag-based indexing in the cache. Defaults to True.
|
330
|
+
size_limit (int, optional): Maximum size of the cache in bytes. Defaults to 10GB.
|
331
|
+
"""
|
179
332
|
super().__init__(
|
180
333
|
executor=executor,
|
181
334
|
cache_name=cache_name,
|
@@ -186,4 +339,14 @@ class JobFunctionCache(FutureCache):
|
|
186
339
|
self.function = function
|
187
340
|
|
188
341
|
def call(self, **kwargs) -> JobFuture:
|
342
|
+
"""Call the wrapped function with the provided arguments.
|
343
|
+
|
344
|
+
This method creates a Job for the function call and submits it through the cache.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
**kwargs: Arguments to pass to the function
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
JobFuture: A future representing the function call
|
351
|
+
"""
|
189
352
|
return self.submit(Job(self.call_count, self.function, kwargs))
|
bencher/plotting/plot_filter.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
from typing import Optional
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from bencher.plotting.plt_cnt_cfg import PltCntCfg
|
5
|
+
import logging
|
5
6
|
import panel as pn
|
6
7
|
|
7
8
|
|
@@ -43,9 +44,20 @@ class VarRange:
|
|
43
44
|
|
44
45
|
return lower_match and upper_match
|
45
46
|
|
46
|
-
def matches_info(self, val, name):
|
47
|
+
def matches_info(self, val: int, name: str) -> tuple[bool, str]:
|
48
|
+
"""Get matching info for a value with a descriptive name.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
val (int): A positive integer to check against the range
|
52
|
+
name (str): A descriptive name for the value being checked, used in the output string
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
tuple[bool, str]: A tuple containing:
|
56
|
+
- bool: True if the value matches the range, False otherwise
|
57
|
+
- str: A formatted string describing the match result
|
58
|
+
"""
|
47
59
|
match = self.matches(val)
|
48
|
-
info = f"{name}\t{
|
60
|
+
info = f"{name}\t{self.lower_bound}>= {val} <={self.upper_bound} is {match}"
|
49
61
|
return match, info
|
50
62
|
|
51
63
|
def __str__(self) -> str:
|
@@ -65,13 +77,21 @@ class PlotFilter:
|
|
65
77
|
input_range: VarRange = VarRange(1, None)
|
66
78
|
|
67
79
|
def matches_result(
|
68
|
-
self, plt_cnt_cfg: PltCntCfg, plot_name: str, override: bool
|
80
|
+
self, plt_cnt_cfg: PltCntCfg, plot_name: str, override: bool
|
69
81
|
) -> PlotMatchesResult:
|
70
|
-
"""Checks if the result data signature matches the type of data the plot is able to display.
|
82
|
+
"""Checks if the result data signature matches the type of data the plot is able to display.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
plt_cnt_cfg (PltCntCfg): Configuration containing counts of different plot elements
|
86
|
+
plot_name (str): Name of the plot being checked
|
87
|
+
override (bool): Whether to override filter matching rules
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
PlotMatchesResult: Object containing match results and information
|
91
|
+
"""
|
71
92
|
return PlotMatchesResult(self, plt_cnt_cfg, plot_name, override)
|
72
93
|
|
73
94
|
|
74
|
-
# @dataclass
|
75
95
|
class PlotMatchesResult:
|
76
96
|
"""Stores information about which properties match the requirements of a particular plotter"""
|
77
97
|
|
@@ -80,12 +100,20 @@ class PlotMatchesResult:
|
|
80
100
|
plot_filter: PlotFilter,
|
81
101
|
plt_cnt_cfg: PltCntCfg,
|
82
102
|
plot_name: str,
|
83
|
-
override: bool
|
84
|
-
):
|
85
|
-
|
86
|
-
matches = []
|
103
|
+
override: bool,
|
104
|
+
) -> None:
|
105
|
+
"""Initialize a PlotMatchesResult with filter matching information.
|
87
106
|
|
88
|
-
|
107
|
+
Args:
|
108
|
+
plot_filter (PlotFilter): The filter defining acceptable ranges for plot properties
|
109
|
+
plt_cnt_cfg (PltCntCfg): Configuration containing counts of different plot elements
|
110
|
+
plot_name (str): Name of the plot being checked
|
111
|
+
override (bool): Whether to override filter matching rules
|
112
|
+
"""
|
113
|
+
match_info: list[str] = []
|
114
|
+
matches: list[bool] = []
|
115
|
+
|
116
|
+
match_candidates: list[tuple[VarRange, int, str]] = [
|
89
117
|
(plot_filter.float_range, plt_cnt_cfg.float_cnt, "float"),
|
90
118
|
(plot_filter.cat_range, plt_cnt_cfg.cat_cnt, "cat"),
|
91
119
|
(plot_filter.vector_len, plt_cnt_cfg.vector_len, "vec"),
|
@@ -99,7 +127,7 @@ class PlotMatchesResult:
|
|
99
127
|
match, info = m.matches_info(cnt, name)
|
100
128
|
matches.append(match)
|
101
129
|
if not match:
|
102
|
-
match_info.append(info)
|
130
|
+
match_info.append(f"\t{info}")
|
103
131
|
if override:
|
104
132
|
match_info.append(f"override: {override}")
|
105
133
|
self.overall = True
|
@@ -107,15 +135,22 @@ class PlotMatchesResult:
|
|
107
135
|
self.overall = all(matches)
|
108
136
|
|
109
137
|
match_info.insert(0, f"plot {plot_name} matches: {self.overall}")
|
110
|
-
self.matches_info = "\n".join(match_info).strip()
|
111
|
-
self.plt_cnt_cfg = plt_cnt_cfg
|
138
|
+
self.matches_info: str = "\n".join(match_info).strip()
|
139
|
+
self.plt_cnt_cfg: PltCntCfg = plt_cnt_cfg
|
112
140
|
|
113
|
-
if self.plt_cnt_cfg.print_debug:
|
114
|
-
|
115
|
-
if not self.overall:
|
116
|
-
print(self.matches_info)
|
141
|
+
# if self.plt_cnt_cfg.print_debug:
|
142
|
+
logging.info(self.matches_info)
|
117
143
|
|
118
144
|
def to_panel(self, **kwargs) -> Optional[pn.pane.Markdown]:
|
145
|
+
"""Convert match information to a Panel Markdown pane if debug mode is enabled.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
**kwargs: Additional keyword arguments to pass to the Panel Markdown constructor
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
Optional[pn.pane.Markdown]: A Markdown pane containing match information if in debug mode,
|
152
|
+
None otherwise
|
153
|
+
"""
|
119
154
|
if self.plt_cnt_cfg.print_debug:
|
120
155
|
return pn.pane.Markdown(self.matches_info, **kwargs)
|
121
156
|
return None
|