holobench 1.8.0__tar.gz → 1.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {holobench-1.8.0 → holobench-1.9.0}/PKG-INFO +1 -1
  2. {holobench-1.8.0 → holobench-1.9.0}/bencher/bench_runner.py +2 -1
  3. {holobench-1.8.0 → holobench-1.9.0}/bencher/bencher.py +7 -2
  4. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_image.py +15 -4
  5. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/bench_result_base.py +58 -11
  6. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/composable_container/composable_container_base.py +4 -2
  7. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/holoview_result.py +1 -0
  8. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/panel_result.py +13 -2
  9. {holobench-1.8.0 → holobench-1.9.0}/bencher/utils.py +1 -1
  10. {holobench-1.8.0 → holobench-1.9.0}/bencher/variables/inputs.py +5 -0
  11. {holobench-1.8.0 → holobench-1.9.0}/bencher/variables/sweep_base.py +1 -1
  12. {holobench-1.8.0 → holobench-1.9.0}/bencher/video_writer.py +1 -1
  13. {holobench-1.8.0 → holobench-1.9.0}/pyproject.toml +1 -1
  14. {holobench-1.8.0 → holobench-1.9.0}/README.md +0 -0
  15. {holobench-1.8.0 → holobench-1.9.0}/bencher/__init__.py +0 -0
  16. {holobench-1.8.0 → holobench-1.9.0}/bencher/bench_cfg.py +0 -0
  17. {holobench-1.8.0 → holobench-1.9.0}/bencher/bench_plot_server.py +0 -0
  18. {holobench-1.8.0 → holobench-1.9.0}/bencher/bench_report.py +0 -0
  19. {holobench-1.8.0 → holobench-1.9.0}/bencher/caching.py +0 -0
  20. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/__init__.py +0 -0
  21. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/benchmark_data.py +0 -0
  22. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_all.py +0 -0
  23. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_categorical.py +0 -0
  24. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_custom_sweep.py +0 -0
  25. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_docs.py +0 -0
  26. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_float3D.py +0 -0
  27. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_float_cat.py +0 -0
  28. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_floats.py +0 -0
  29. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_floats2D.py +0 -0
  30. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_holosweep.py +0 -0
  31. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_holosweep_objects.py +0 -0
  32. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_holosweep_tap.py +0 -0
  33. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_levels.py +0 -0
  34. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_pareto.py +0 -0
  35. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_sample_cache.py +0 -0
  36. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_sample_cache_context.py +0 -0
  37. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_simple.py +0 -0
  38. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_simple_bool.py +0 -0
  39. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_simple_cat.py +0 -0
  40. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_simple_float.py +0 -0
  41. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_strings.py +0 -0
  42. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_time_event.py +0 -0
  43. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_video.py +0 -0
  44. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/example_workflow.py +0 -0
  45. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_bokeh_plotly.py +0 -0
  46. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_hover_ex.py +0 -0
  47. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_hvplot_explorer.py +0 -0
  48. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_interactive.py +0 -0
  49. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_streamnd.py +0 -0
  50. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_streams.py +0 -0
  51. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_template.py +0 -0
  52. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_updates.py +0 -0
  53. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/experimental/example_vector.py +0 -0
  54. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/meta/example_meta.py +0 -0
  55. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/meta/example_meta_cat.py +0 -0
  56. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/meta/example_meta_float.py +0 -0
  57. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/meta/example_meta_levels.py +0 -0
  58. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/optuna/example_optuna.py +0 -0
  59. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/shelved/example_float2D_scatter.py +0 -0
  60. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/shelved/example_float3D_cone.py +0 -0
  61. {holobench-1.8.0 → holobench-1.9.0}/bencher/example/shelved/example_kwargs.py +0 -0
  62. {holobench-1.8.0 → holobench-1.9.0}/bencher/job.py +0 -0
  63. {holobench-1.8.0 → holobench-1.9.0}/bencher/optuna_conversions.py +0 -0
  64. {holobench-1.8.0 → holobench-1.9.0}/bencher/plotting/__init__.py +0 -0
  65. {holobench-1.8.0 → holobench-1.9.0}/bencher/plotting/plot_filter.py +0 -0
  66. {holobench-1.8.0 → holobench-1.9.0}/bencher/plotting/plt_cnt_cfg.py +0 -0
  67. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/__init__.py +0 -0
  68. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/bench_result.py +0 -0
  69. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/composable_container/__init__.py +0 -0
  70. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/composable_container/composable_container_panel.py +0 -0
  71. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/composable_container/composable_container_video.py +0 -0
  72. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/float_formatter.py +0 -0
  73. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/optuna_result.py +0 -0
  74. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/plotly_result.py +0 -0
  75. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/video_result.py +0 -0
  76. {holobench-1.8.0 → holobench-1.9.0}/bencher/results/video_summary.py +0 -0
  77. {holobench-1.8.0 → holobench-1.9.0}/bencher/variables/parametrised_sweep.py +0 -0
  78. {holobench-1.8.0 → holobench-1.9.0}/bencher/variables/results.py +0 -0
  79. {holobench-1.8.0 → holobench-1.9.0}/bencher/variables/time.py +0 -0
  80. {holobench-1.8.0 → holobench-1.9.0}/bencher/worker_job.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: holobench
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: A package for benchmarking the performance of arbitrary functions
5
5
  Author-email: Austin Gregg-Smith <blooop@gmail.com>
6
6
  Description-Content-Type: text/markdown
@@ -103,7 +103,7 @@ class BenchRunner:
103
103
  for r in range(1, repeats + 1):
104
104
  for lvl in range(min_level, max_level + 1):
105
105
  if grouped:
106
- report_level = BenchReport(f"{self.name}_{run_cfg.run_tag}")
106
+ report_level = BenchReport(f"{run_cfg.run_tag}_{self.name}")
107
107
 
108
108
  for bch_fn in self.bench_fns:
109
109
  run_lvl = deepcopy(run_cfg)
@@ -114,6 +114,7 @@ class BenchRunner:
114
114
  res = bch_fn(run_lvl, report_level)
115
115
  else:
116
116
  res = bch_fn(run_lvl, BenchReport())
117
+ res.report.bench_name = f"{run_cfg.run_tag}_{res.report.bench_name}"
117
118
  self.show_publish(res.report, show, publish, save, debug)
118
119
  self.results.append(res)
119
120
  if grouped:
@@ -175,8 +175,13 @@ class Bench(BenchPlotServer):
175
175
  self.plot_callbacks = []
176
176
  self.plot = True
177
177
 
178
- def add_plot_callback(self, callback: Callable[[BenchResult], pn.panel]) -> None:
179
- self.plot_callbacks.append(callback)
178
+ def add_plot_callback(self, callback: Callable[[BenchResult], pn.panel], **kwargs) -> None:
179
+ """Add a plotting callback that will be called on any result produced when calling a sweep funciton. You can pass additional arguments to the plotting function with kwargs. e.g. add_plot_callback(bch.BenchResult.to_video_grid,)
180
+
181
+ Args:
182
+ callback (Callable[[BenchResult], pn.panel]): _description_
183
+ """
184
+ self.plot_callbacks.append(partial(callback, **kwargs))
180
185
 
181
186
  def set_worker(self, worker: Callable, worker_input_cfg: ParametrizedSweep = None) -> None:
182
187
  """Set the benchmark worker function and optionally the type the worker expects
@@ -18,6 +18,7 @@ class BenchPolygons(bch.ParametrizedSweep):
18
18
  linestyle = bch.StringSweep(["solid", "dashed", "dotted"])
19
19
  color = bch.StringSweep(["red", "green", "blue"])
20
20
  polygon = bch.ResultImage()
21
+ area = bch.ResultVar()
21
22
  # hmap = bch.ResultHmap()
22
23
 
23
24
  def __call__(self, **kwargs):
@@ -25,6 +26,7 @@ class BenchPolygons(bch.ParametrizedSweep):
25
26
  points = polygon_points(self.radius, self.sides)
26
27
  # self.hmap = hv.Curve(points)
27
28
  self.polygon = self.points_to_polygon_png(points, bch.gen_image_path("polygon"))
29
+ self.area = self.radius * self.sides
28
30
  return super().__call__()
29
31
 
30
32
  def points_to_polygon_png(self, points: list[float], filename: str):
@@ -52,17 +54,26 @@ class BenchPolygons(bch.ParametrizedSweep):
52
54
  def example_image(
53
55
  run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
54
56
  ) -> bch.Bench:
57
+ run_cfg.use_cache = False
55
58
  bench = bch.Bench("polygons", BenchPolygons(), run_cfg=run_cfg, report=report)
56
59
 
60
+ bench.result_vars = ["polygon", "area"]
61
+
62
+ bench.add_plot_callback(bch.BenchResult.to_sweep_summary)
63
+ # bench.add_plot_callback(bch.BenchResult.to_auto, level=2)
64
+ bench.add_plot_callback(bch.BenchResult.to_panes, level=3)
65
+ # bench.add_plot_callback(bch.BenchResult.to_panes)
66
+
57
67
  sweep_vars = ["sides", "radius", "linewidth", "color"]
68
+
69
+ # sweep_vars = ["sides", "radius" ]
70
+
58
71
  for i in range(1, len(sweep_vars)):
59
72
  s = sweep_vars[:i]
60
73
  bench.plot_sweep(
61
74
  f"Polygons Sweeping {len(s)} Parameters",
62
75
  input_vars=s,
63
- result_vars=[BenchPolygons.param.polygon],
64
76
  )
65
-
66
77
  return bench
67
78
 
68
79
 
@@ -97,6 +108,6 @@ if __name__ == "__main__":
97
108
  # ex_run_cfg.debug = True
98
109
  # ex_run_cfg.repeats = 2
99
110
  ex_run_cfg.level = 4
100
- example_image_vid(ex_run_cfg).report.show()
111
+ # example_image_vid(ex_run_cfg).report.show()
101
112
  # example_image_vid_sequential(ex_run_cfg).report.show()
102
- # example_image(ex_run_cfg).report.show()
113
+ example_image(ex_run_cfg).report.show()
@@ -10,6 +10,8 @@ import panel as pn
10
10
  from bencher.utils import int_to_col, color_tuple_to_css, callable_name
11
11
 
12
12
  from bencher.variables.parametrised_sweep import ParametrizedSweep
13
+ from bencher.variables.inputs import with_level
14
+
13
15
  from bencher.variables.results import OptDir
14
16
  from copy import deepcopy
15
17
  from bencher.results.optuna_result import OptunaResult
@@ -21,6 +23,7 @@ from bencher.variables.results import (
21
23
  )
22
24
 
23
25
  from bencher.results.composable_container.composable_container_panel import ComposableContainerPanel
26
+ from bencher.utils import listify
24
27
 
25
28
  # todo add plugins
26
29
  # https://gist.github.com/dorneanu/cce1cd6711969d581873a88e0257e312
@@ -54,7 +57,7 @@ class BenchResultBase(OptunaResult):
54
57
  return self.ds.count()
55
58
 
56
59
  def to_hv_dataset(
57
- self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None
60
+ self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None, level: int = None
58
61
  ) -> hv.Dataset:
59
62
  """Generate a holoviews dataset from the xarray dataset.
60
63
 
@@ -67,11 +70,11 @@ class BenchResultBase(OptunaResult):
67
70
 
68
71
  if reduce == ReduceType.NONE:
69
72
  kdims = [i.name for i in self.bench_cfg.all_vars]
70
- return hv.Dataset(self.to_dataset(reduce, result_var), kdims=kdims)
71
- return hv.Dataset(self.to_dataset(reduce, result_var))
73
+ return hv.Dataset(self.to_dataset(reduce, result_var, level), kdims=kdims)
74
+ return hv.Dataset(self.to_dataset(reduce, result_var, level))
72
75
 
73
76
  def to_dataset(
74
- self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None
77
+ self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None, level: int = None
75
78
  ) -> xr.Dataset:
76
79
  """Generate a summarised xarray dataset.
77
80
 
@@ -84,20 +87,25 @@ class BenchResultBase(OptunaResult):
84
87
  if reduce == ReduceType.AUTO:
85
88
  reduce = ReduceType.REDUCE if self.bench_cfg.repeats > 1 else ReduceType.SQUEEZE
86
89
 
87
- ds = self.ds if result_var is None else self.ds[result_var.name]
90
+ ds_out = self.ds if result_var is None else self.ds[result_var.name]
88
91
 
89
92
  match (reduce):
90
93
  case ReduceType.REDUCE:
91
- ds_reduce_mean = ds.mean(dim="repeat", keep_attrs=True)
92
- ds_reduce_std = ds.std(dim="repeat", keep_attrs=True)
94
+ ds_reduce_mean = ds_out.mean(dim="repeat", keep_attrs=True)
95
+ ds_reduce_std = ds_out.std(dim="repeat", keep_attrs=True)
93
96
 
94
97
  for v in ds_reduce_mean.data_vars:
95
98
  ds_reduce_mean[f"{v}_std"] = ds_reduce_std[v]
96
- return ds_reduce_mean
99
+ ds_out = ds_reduce_mean
97
100
  case ReduceType.SQUEEZE:
98
- return ds.squeeze(drop=True)
99
- case _:
100
- return ds
101
+ ds_out = ds_out.squeeze(drop=True)
102
+ if level is not None:
103
+ coords_no_repeat = {}
104
+ for c, v in ds_out.coords.items():
105
+ if c != "repeat":
106
+ coords_no_repeat[c] = with_level(v.to_numpy(), level)
107
+ return ds_out.sel(coords_no_repeat)
108
+ return ds_out
101
109
 
102
110
  def get_optimal_vec(
103
111
  self,
@@ -405,6 +413,45 @@ class BenchResultBase(OptunaResult):
405
413
  return container(val, styles={"background": "white"}, **kwargs)
406
414
  return val
407
415
 
416
+ @staticmethod
417
+ def select_level(
418
+ dataset: xr.Dataset,
419
+ level: int,
420
+ include_types: List[type] = None,
421
+ exclude_names: List[str] = None,
422
+ ) -> xr.Dataset:
423
+ """Given a dataset, return a reduced dataset that only contains data from a specified level. By default all types of variables are filtered at the specified level. If you only want to get a reduced level for some types of data you can pass in a list of types to get filtered, You can also pass a list of variables names to exclude from getting filtered
424
+ Args:
425
+ dataset (xr.Dataset): dataset to filter
426
+ level (int): desired data resolution level
427
+ include_types (List[type], optional): Only filter data of these types. Defaults to None.
428
+ exclude_names (List[str], optional): Only filter data with these variable names. Defaults to None.
429
+
430
+ Returns:
431
+ xr.Dataset: A reduced dataset at the specified level
432
+
433
+ Example: a dataset with float_var: [1,2,3,4,5] cat_var: [a,b,c,d,e]
434
+
435
+ select_level(ds,2) -> [1,5] [a,e]
436
+ select_level(ds,2,(float)) -> [1,5] [a,b,c,d,e]
437
+ select_level(ds,2,exclude_names=["cat_var]) -> [1,5] [a,b,c,d,e]
438
+
439
+ see test_bench_result_base.py -> test_select_level()
440
+ """
441
+ coords_no_repeat = {}
442
+ for c, v in dataset.coords.items():
443
+ if c != "repeat":
444
+ vals = v.to_numpy()
445
+ print(vals.dtype)
446
+ include = True
447
+ if include_types is not None and vals.dtype not in listify(include_types):
448
+ include = False
449
+ if exclude_names is not None and c in listify(exclude_names):
450
+ include = False
451
+ if include:
452
+ coords_no_repeat[c] = with_level(v.to_numpy(), level)
453
+ return dataset.sel(coords_no_repeat)
454
+
408
455
  # MAPPING TO LOWER LEVEL BENCHCFG functions so they are available at a top level.
409
456
  def to_sweep_summary(self, **kwargs):
410
457
  return self.bench_cfg.to_sweep_summary(**kwargs)
@@ -36,9 +36,11 @@ class ComposableContainerBase:
36
36
  return f"{var_value}"
37
37
  return None
38
38
 
39
- def __init__(self, horizontal: bool = True) -> None:
39
+ def __init__(
40
+ self, horizontal: bool = True, compose_method: ComposeType = ComposeType.right
41
+ ) -> None:
40
42
  self.horizontal: bool = horizontal
41
- self.compose_method = ComposeType.right
43
+ self.compose_method = compose_method
42
44
  self.container = []
43
45
 
44
46
  def append(self, obj: Any) -> None:
@@ -130,6 +130,7 @@ class HoloviewResult(PanelResult):
130
130
  float_range=VarRange(1, 1),
131
131
  cat_range=VarRange(0, None),
132
132
  repeats_range=VarRange(1, 1),
133
+ panel_range=VarRange(0, None),
133
134
  reduce=ReduceType.SQUEEZE,
134
135
  target_dimension=2,
135
136
  result_var=result_var,
@@ -2,6 +2,7 @@ from typing import Optional
2
2
  from functools import partial
3
3
  import panel as pn
4
4
  from param import Parameter
5
+ import holoviews as hv
5
6
  from bencher.results.bench_result_base import BenchResultBase, ReduceType
6
7
  from bencher.results.video_result import VideoControls
7
8
  from bencher.variables.results import (
@@ -18,13 +19,23 @@ class PanelResult(BenchResultBase):
18
19
  )
19
20
 
20
21
  def to_panes(
21
- self, result_var: Parameter = None, target_dimension: int = 0, container=None, **kwargs
22
+ self,
23
+ result_var: Parameter = None,
24
+ hv_dataset=None,
25
+ target_dimension: int = 0,
26
+ container=None,
27
+ level: int = None,
28
+ **kwargs
22
29
  ) -> Optional[pn.pane.panel]:
23
30
  if container is None:
24
31
  container = pn.pane.panel
32
+ if hv_dataset is None:
33
+ hv_dataset = self.to_hv_dataset(ReduceType.SQUEEZE, level=level)
34
+ elif not isinstance(hv_dataset, hv.Dataset):
35
+ hv_dataset = hv.Dataset(hv_dataset)
25
36
  return self.map_plot_panes(
26
37
  partial(self.ds_to_container, container=container),
27
- hv_dataset=self.to_hv_dataset(ReduceType.SQUEEZE),
38
+ hv_dataset=hv_dataset,
28
39
  target_dimension=target_dimension,
29
40
  result_var=result_var,
30
41
  result_types=PANEL_TYPES,
@@ -150,7 +150,7 @@ def callable_name(any_callable: Callable[..., Any]) -> str:
150
150
 
151
151
 
152
152
  def listify(obj) -> list:
153
- """Take an object and turn it into a list if its not already a list"""
153
+ """Take an object and turn it into a list if its not already a list. However if the object is none, don't turn it into a list"""
154
154
  if obj is None:
155
155
  return None
156
156
  if isinstance(obj, list):
@@ -191,3 +191,8 @@ def box(name, center, width):
191
191
  var = FloatSweep(default=center, bounds=(center - width, center + width))
192
192
  var.name = name
193
193
  return var
194
+
195
+
196
+ def with_level(arr: list, level) -> list:
197
+ return IntSweep(sample_values=arr).with_level(level).values()
198
+ # return tmp.with_sample_values(arr).with_level(level).values()
@@ -138,7 +138,7 @@ class SweepBase(param.Parameter):
138
138
  output.step = None # pylint: disable = attribute-defined-outside-init
139
139
  return output
140
140
 
141
- def with_sample_values(self, sample_values: int) -> SweepBase:
141
+ def with_sample_values(self, sample_values: list) -> SweepBase:
142
142
  output = deepcopy(self)
143
143
  # TODO set up class properly. Slightly complicated due to slots
144
144
  try:
@@ -32,7 +32,7 @@ class VideoWriter:
32
32
  return self.filename
33
33
 
34
34
  @staticmethod
35
- def create_label(label, width=None, height=20):
35
+ def create_label(label, width=None, height=14):
36
36
  if width is None:
37
37
  width = len(label) * 8
38
38
  new_img = Image.new("RGB", (width, height), (255, 255, 255))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "holobench"
3
- version = "1.8.0"
3
+ version = "1.9.0"
4
4
 
5
5
  authors = [{ name = "Austin Gregg-Smith", email = "blooop@gmail.com" }]
6
6
  description = "A package for benchmarking the performance of arbitrary functions"
File without changes
File without changes
File without changes
File without changes