Utopia  2
Framework for studying models of complex & adaptive systems.
HdfBench.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_HDFBENCH_HH
2 #define UTOPIA_MODELS_HDFBENCH_HH
3 
4 #include <map>
5 #include <vector>
6 #include <chrono>
7 #include <limits>
8 #include <numeric>
9 #include <functional>
10 #include <thread>
11 #include <iostream>
12 
13 #include <hdf5.h>
14 #include <boost/iterator/counting_iterator.hpp>
15 
16 #include <utopia/core/model.hh>
17 #include <utopia/core/types.hh>
18 
19 
20 namespace Utopia {
21 namespace Models {
22 namespace HdfBench {
23 
26 
27 
29 
35  public Model<HdfBenchModel, HdfBenchModelTypes>
36 {
37 public:
40 
42  using DataSet = typename Base::DataSet;
43 
45  using Config = typename Base::Config;
46 
47 
48  // -- Types for time handling -- //
50  using Clock = std::chrono::high_resolution_clock;
51 
53  using Time = std::chrono::high_resolution_clock::time_point;
54 
56  using DurationType = std::chrono::duration<double>;
57 
59  using BenchFunc = std::function<double(const std::string, Config)>;
60 
61 
62 private:
63  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor
64 
65 
66  // -- Members of this model -- //
68  std::map<std::string, BenchFunc> _setup_funcs;
69 
71  std::map<std::string, BenchFunc> _write_funcs;
72 
74  const std::vector<std::string> _benchmarks;
75 
77  const std::map<std::string, Config> _bench_cfgs;
78 
80  std::map<std::string, double> _times;
81 
82 
83  // -- Datasets -- //
85  std::shared_ptr<DataSet> _dset_times;
86 
88  std::map<std::string, std::shared_ptr<DataSet>> _dsets;
89 
90 
91  // -- Configuration parameters applicable to all benchmarks -- //
93  const bool _delete_afterwards;
94 
96  const std::chrono::duration<double> _sleep_step;
97 
99  const std::chrono::duration<double> _sleep_bench;
100 
101 
102 
103  // -- Construction helper functions -- //
105  std::map<std::string, Config> load_benchmarks() {
106  this->_log->debug("Loading benchmark configurations ...");
107  std::map<std::string, Config> cfg;
108 
109  for (const auto &bname : _benchmarks) {
110  this->_log->trace("Loading benchmark configuration '{}' ...",
111  bname);
112 
113  try {
114  cfg[bname] = get_as<Config>(bname, this->_cfg);
115  }
116  catch (std::exception &e) {
117  std::cerr << "Could not find a benchmark configuration with "
118  "name '" << bname << "'! Make sure the given "
119  "configuration contains such an entry."
120  << std::endl << "Original error message: ";
121  throw;
122  }
123  }
124 
125  this->_log->debug("Got {} benchmark configurations.", cfg.size());
126  return cfg;
127  }
128 
129 public:
131 
141  template<class ParentModel>
143  const std::string& name,
144  ParentModel& parent_model,
145  const DataIO::Config& custom_cfg = {}
146  )
147  :
148  // Initialize first via base model
149  Base(name, parent_model, custom_cfg),
150 
151  // Set maps for setup and write functions
152  _setup_funcs(),
153  _write_funcs(),
154 
155  // Get the set of enabled benchmarks from the config
156  _benchmarks(get_as<std::vector<std::string>>("benchmarks", this->_cfg)),
158 
159  // Create the temporary map for measured times and the times dataset
160  _times(),
161  _dset_times(this->create_dset("times", {_benchmarks.size()})),
162  _dsets(),
163 
164  // Extract config parameters applicable to all benchmarks
165  _delete_afterwards(get_as<bool>("delete_afterwards", this->_cfg)),
166  _sleep_step(get_as<double>("sleep_step", this->_cfg)),
167  _sleep_bench(get_as<double>("sleep_bench", this->_cfg))
168  {
169  // Check arguments
170  if (_delete_afterwards) {
171  throw std::invalid_argument("delete_afterwards feature is not yet "
172  "implemented!");
173  }
174 
175  // Set up the function mappings . . . . . . . . . . . . . . . . . . . .
176  // FIXME Creating func maps should be possible in initializer list, but
177  // although it compiles, it leads to segfaults ...
178 
179  this->_log->debug("Associating setup functions ...");
180  _setup_funcs["setup_nd"] = setup_nd;
181  _setup_funcs["setup_nd_with_chunks"] = setup_nd_with_chunks;
182 
183 
184  this->_log->debug("Associating write functions ...");
185  _write_funcs["write_const"] = write_const;
186 
187 
188  this->_log->debug("Associated {} setup and {} write function(s).",
189  _setup_funcs.size(), _write_funcs.size());
190 
191 
192  // Carry out the setup benchmark . . . . . . . . . . . . . . . . . . .
193  const bool initial_write = get_as<bool>("initial_write", this->_cfg);
194  this->_log->debug("initial_write: {}, sleep_step: {}s, "
195  "sleep_bench: {}s", initial_write ? "yes" : "no",
196  _sleep_step.count(), _sleep_bench.count());
197 
198  this->_log->info("Performing setup and initial benchmarks ...");
199 
200  for (const auto &bname : _benchmarks) {
201  // Setup the dataset and store the time needed
202  _times[bname] = this->benchmark<true>(bname);
203 
204  // Perform one write operation, if configured to do so, and add
205  // the time on top
206  if (initial_write) {
207  _times[bname] += this->benchmark(bname);
208  }
209  }
210 
211 
212  // Add information to the dataset attributes
213  _dset_times->add_attribute("dim_name__1", "benchmark");
214  _dset_times->add_attribute("coords__benchmark", _benchmarks);
215  _dset_times->add_attribute("initial_write", initial_write);
216 
217  // Done now.
218  this->_log->debug("Finished constructing HdfBench '{}'.", this->_name);
219  }
220 
221  // Runtime functions ......................................................
222 
229  void perform_step () {
230  // Sleep before the actual step is carried out
231  std::this_thread::sleep_for(_sleep_step);
232  // NOTE Duration might be zero, not triggering a sleep. Same below.
233 
234  // Carry out the benchmarks, optionally sleeping some time before that
235  for (const auto &bname : _benchmarks) {
236  std::this_thread::sleep_for(_sleep_bench);
237 
238  _times[bname] = this->benchmark(bname);
239  }
240  }
241 
242 
244 
249  void monitor ()
250  {
251  // Can supply information to the monitor here in two ways:
252  // this->_monitor.set_entry("key", value);
253  // this->_monitor.set_entry("key", [this](){return 42.;});
254  }
255 
256 
258  void write_data () {
259  _dset_times->write(_benchmarks.begin(), _benchmarks.end(),
260  [this](const auto& bname) {
261  return this->_times.at(bname);
262  });
263  }
264 
265 
266 protected:
267 
268  // Benchmarking ...........................................................
269 
271  template<bool setup=false>
272  double benchmark(const std::string &bname) {
273  // Get the benchmark configuration entry
274  const auto bcfg = _bench_cfgs.at(bname);
275 
276  // Get the name of the setup/benchmark function, then resolve it
277  BenchFunc bfunc;
278  if constexpr (setup) {
279  bfunc = _setup_funcs.at(get_as<std::string>("setup_func", bcfg));
280  }
281  else {
282  bfunc = _write_funcs.at(get_as<std::string>("write_func", bcfg));
283  }
284 
285  // Call the function; its return value is the time it took to execute
286  const auto btime = bfunc(bname, bcfg);
287 
288  // Log the time, then return it
289  this->_log->debug("Benchmark result {:>20s} {} : {:>10.3f} ms",
290  bname, setup ? "setup" : "write", btime * 1E3);
291  return btime;
292  }
293 
295  double time_since(const Time start) {
296  return time_between(start, Clock::now());
297  }
298 
300  double time_between(const Time start, const Time end) {
301  const DurationType seconds = abs(end - start);
302  return seconds.count();
303  }
304 
305 
306  // Setup functions ........................................................
307 
308  /* @brief Sets up an n-dimensional dataset
309  * @details The dataset shape corresponds to the write_shape argument, but
310  * with an extra dimension in front that has as extend time_max + 1
311  */
312  BenchFunc setup_nd = [this](const auto& bname, auto cfg){
313  // Determine the shape of the final dataset
314  auto shape = get_as<std::vector<hsize_t>>("write_shape", cfg);
315  shape.insert(shape.begin(), this->get_time_max() + 1);
316 
317  const auto start = Clock::now();
318  // -- benchmark start -- //
319 
320  // Create the dataset and set its capacity
321  _dsets[bname] = this->_hdfgrp->open_dataset(bname);
322  _dsets[bname]->set_capacity(shape);
323 
324  // --- benchmark end --- //
325  return time_since(start);
326  };
327 
328 
329  BenchFunc setup_nd_with_chunks = [this](const auto& bname, auto cfg){
330  // Call the regular setup_nd to set up the dataset
331  const auto time_setup = this->setup_nd(bname, cfg);
332 
333  // Extract the chunks argument
334  const auto chunks = get_as<std::vector<hsize_t>>("chunks", cfg);
335 
336  const auto start = Clock::now();
337  // -- benchmark start -- //
338 
339  // Set the chunks value
340  _dsets[bname]->set_chunksize(chunks);
341 
342  // --- benchmark end --- //
343  return time_setup + time_since(start);
344  };
345 
346 
347  // Write functions ........................................................
348 
350  BenchFunc write_const = [this](const auto& bname, auto cfg){
351  // Determine the value to write
352  const auto val = get_as<double>("const_val", cfg);
353 
354  // Determine iterator length by factorizing the shape
355  const auto shape = get_as<std::vector<std::size_t>>("write_shape", cfg);
356  const auto it_len = std::accumulate(shape.begin(), shape.end(),
357  1, std::multiplies<std::size_t>());
358 
359  const auto start = Clock::now();
360  // -- benchmark start -- //
361 
362  // Can use the counting iterator as surrogate
363  _dsets[bname]->write(boost::counting_iterator<std::size_t>(0),
364  boost::counting_iterator<std::size_t>(it_len),
365  [&val]([[maybe_unused]] auto &count){
366  return val;
367  });
368 
369  // --- benchmark end --- //
370  return time_since(start);
371  };
372 };
373 
374 
375 } // namespace HdfBench
376 } // namespace Models
377 } // namespace Utopia
378 
379 #endif // UTOPIA_MODELS_HDFBENCH_HH
Base class interface for Models using the CRT Pattern.
Definition: model.hh:112
const std::shared_ptr< DataGroup > _hdfgrp
The HDF group this model instance should write its data to.
Definition: model.hh:176
Time get_time_max() const
Return the maximum time possible for this model.
Definition: model.hh:403
typename ModelTypes::DataSet DataSet
Data type that is used for storing data.
Definition: model.hh:125
const Config _cfg
Config node belonging to this model instance.
Definition: model.hh:158
const std::string _name
Name of the model instance.
Definition: model.hh:149
std::shared_ptr< DataSet > create_dset(const std::string name, const std::shared_ptr< DataGroup > &hdfgrp, std::vector< hsize_t > add_write_shape, const std::size_t compression_level=1, const std::vector< hsize_t > chunksize={})
Create a new dataset within the given group.
Definition: model.hh:752
typename ModelTypes::Config Config
Data type that holds the configuration.
Definition: model.hh:116
const std::shared_ptr< spdlog::logger > _log
The (model) logger.
Definition: model.hh:164
The HdfBench Model.
Definition: HdfBench.hh:36
void monitor()
Monitor model information.
Definition: HdfBench.hh:249
const std::vector< std::string > _benchmarks
Names of benchmarks.
Definition: HdfBench.hh:74
typename Base::DataSet DataSet
Data type for a dataset.
Definition: HdfBench.hh:42
std::map< std::string, double > _times
The results of the measurements, stored under the benchmark name.
Definition: HdfBench.hh:80
double time_since(const Time start)
Returns the time (in seconds) since the given time point.
Definition: HdfBench.hh:295
std::shared_ptr< DataSet > _dset_times
Dataset to store the write times in.
Definition: HdfBench.hh:85
double benchmark(const std::string &bname)
Carries out the benchmark associated with the given name.
Definition: HdfBench.hh:272
HdfBenchModel(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the HdfBench model.
Definition: HdfBench.hh:142
std::function< double(const std::string, Config)> BenchFunc
Type of a benchmark function pointer.
Definition: HdfBench.hh:59
Model< HdfBenchModel, HdfBenchModelTypes > Base
The base model type.
Definition: HdfBench.hh:39
const std::chrono::duration< double > _sleep_step
Sleep time in seconds at the beginning of each step.
Definition: HdfBench.hh:96
std::chrono::high_resolution_clock Clock
Type of clock.
Definition: HdfBench.hh:50
std::map< std::string, std::shared_ptr< DataSet > > _dsets
Dataset to write test data to are stored in a map of dataset pointers.
Definition: HdfBench.hh:88
void write_data()
Write the result times of each benchmark.
Definition: HdfBench.hh:258
std::map< std::string, Config > load_benchmarks()
Load the benchmark configurations into a map.
Definition: HdfBench.hh:105
double time_between(const Time start, const Time end)
Returns the absolute time (in seconds) between the given time points.
Definition: HdfBench.hh:300
typename Base::Config Config
Data type that holds the configuration.
Definition: HdfBench.hh:45
std::chrono::duration< double > DurationType
Type of the duration measure, should be a floating-point type.
Definition: HdfBench.hh:56
const std::chrono::duration< double > _sleep_bench
Sleep time in seconds before each benchmark.
Definition: HdfBench.hh:99
BenchFunc write_const
Writes a constant value into the dataset.
Definition: HdfBench.hh:350
const std::map< std::string, Config > _bench_cfgs
Configuration for the benchmarks.
Definition: HdfBench.hh:77
BenchFunc setup_nd_with_chunks
Definition: HdfBench.hh:329
std::map< std::string, BenchFunc > _write_funcs
A map of implemented write functions.
Definition: HdfBench.hh:71
const bool _delete_afterwards
Whether to delete datasets after the last step.
Definition: HdfBench.hh:93
std::chrono::high_resolution_clock::time_point Time
Type of a time point, retrieved from the clock.
Definition: HdfBench.hh:53
void perform_step()
Iterate a single step.
Definition: HdfBench.hh:229
std::map< std::string, BenchFunc > _setup_funcs
A map of implemented setup functions for datasets.
Definition: HdfBench.hh:68
BenchFunc setup_nd
Definition: HdfBench.hh:312
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
ReturnType get_as(const std::string &key, const DataIO::Config &node)
This function is a wrapper around the yaml-cpp YAML::Node::as function.
Definition: cfg_utils.hh:158
auto end(zip< Containers... > &zipper)
end function like std::end
Definition: zip.hh:550
Definition: agent.hh:11
Wrapper struct for defining model class data types.
Definition: model.hh:92