Optimized Belief Propagation (CPU and GPU)
EvaluateAcrossRuns.cpp
Go to the documentation of this file.
1 /*
2 Copyright (C) 2024 Scott Grauer-Gray
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
28 #include <fstream>
29 #include <algorithm>
30 #include <set>
31 #include <utility>
32 #include <iostream>
33 #include <algorithm>
35 #include "EvaluateAcrossRuns.h"
36 
37 //process runtime and speedup data across multiple runs (likely on different
38 //architectures) from csv files corresponding to each run and write results to
39 //file where the runtimes and speedups for each run are shown in a single file
40 //and where the runs are displayed left to right from fastest to slowest
42  const std::filesystem::path& imp_results_file_path,
43  const std::vector<std::string>& eval_across_runs_top_text,
44  const std::vector<std::string>& eval_across_runs_in_params_show) const
45 {
46  //retrieve names of runs with results
47  //run names usually correspond to architecture of run
48  const std::vector<std::string> run_names =
49  GetRunNames(imp_results_file_path);
50 
51  //initialize vector of run results with speedups for each run name
52  std::map<std::string, RunResultsSpeedups> run_results_by_name;
53  for (const auto& run_name : run_names) {
54  run_results_by_name.insert(
55  {run_name, RunResultsSpeedups(imp_results_file_path, run_name)});
56  }
57 
58  //genereation data mappings for evaluation including run results and speedups
59  //for each run, mapping from input signature to runtime, and run inputs to
60  //parameters to display in evaluation across runs as well as speedup headers
61  //in order
62  const EvalAcrossRunsData eval_data =
63  GenEvalAcrossRunsData(
64  run_results_by_name,
65  eval_across_runs_in_params_show);
66 
67  //get run names in order from fastest to slowest based on first speedup
68  //header
69  std::vector<std::string> run_names_ordered =
70  OrderedRunNames(eval_data);
71 
72  //add any run names not in ordered run names (due to not having data
73  //corresponding to first speedup header) to end of ordered runs
74  std::ranges::copy_if(
75  run_names,
76  std::back_inserter(run_names_ordered),
77  [&run_names_ordered](const auto& run_name) {
78  return (
79  std::ranges::find(
80  run_names_ordered,
81  run_name) == run_names_ordered.end());
82  });
83 
84  //write output evaluation across runs to file
85  WriteEvalAcrossRunsToFile(
86  imp_results_file_path,
87  eval_across_runs_top_text,
88  eval_across_runs_in_params_show,
89  eval_data,
90  run_names_ordered);
91 }
92 
101 EvalAcrossRunsData EvaluateAcrossRuns::GenEvalAcrossRunsData(
102  const std::map<std::string, RunResultsSpeedups>& run_results_by_name,
103  const std::vector<std::string>& eval_across_runs_in_params_show) const
104 {
105  EvalAcrossRunsData eval_data;
106  for (const auto& [run_name, run_results] : run_results_by_name)
107  {
108  eval_data.speedup_results_name_to_data.insert(
109  {run_name,
110  run_results.Speedups()});
111  eval_data.input_to_runtime_across_archs.insert(
112  {run_name,
113  run_results.InputsToKeyVal(run_eval::kOptimizedRuntimeHeader)});
114  const auto& inputs_to_runtimes = run_results.InputsToKeyVal(
116  //go through inputs for current run results
117  //need to go through every run so that all inputs in every run are included
118  for (const auto& [input_sig, _] : inputs_to_runtimes) {
119  //check if input already addded to set of inputs
120  if (!(eval_data.inputs_to_params_disp_ordered.contains(input_sig))) {
121  eval_data.inputs_to_params_disp_ordered.insert(
122  {input_sig, std::vector<std::string>()});
123  //add input parameters to display in evaluation across runs
124  for (const auto& disp_param : eval_across_runs_in_params_show) {
125  eval_data.inputs_to_params_disp_ordered.at(input_sig).push_back(
126  run_results.DataForInput(input_sig).at(disp_param));
127  }
128  }
129  }
130  //go through speedups for run and add to speedup headers if not already
131  //included
132  const auto run_speedups_ordered = run_results.SpeedupHeadersOrder();
133  for (auto run_speedup_iter = run_speedups_ordered.cbegin();
134  run_speedup_iter < run_speedups_ordered.cend();
135  run_speedup_iter++)
136  {
137  //ignore speedup if all whitespace
138  if (std::ranges::all_of(
139  *run_speedup_iter,
140  [](unsigned char c){ return std::isspace(c); }))
141  {
142  continue;
143  }
144  //check if speedup in run is included in current evaluation speedups and
145  //add it in expected position in evaluation speedups if not
146  if (std::ranges::none_of(eval_data.speedup_headers,
147  [run_speedup_iter](const auto& header) {
148  return (header == *run_speedup_iter);
149  }))
150  {
151  //get relative position of previous ordered speedup header
152  auto iter_prev_ordered_header = run_speedup_iter;
153  while (iter_prev_ordered_header != run_speedups_ordered.cbegin())
154  {
155  iter_prev_ordered_header--;
156  if (std::ranges::find(eval_data.speedup_headers,
157  *iter_prev_ordered_header) !=
158  eval_data.speedup_headers.cend())
159  {
160  break;
161  }
162  }
163 
164  //add speedup header in front of previous ordered speedup header if
165  //not first speedup header for run and previous ordered header exists
166  //in speedup headers
167  if (iter_prev_ordered_header != run_speedups_ordered.cbegin())
168  {
169  //find position in evaluation speedups of previous ordered header
170  //and add new header in front of it
171  eval_data.speedup_headers.insert(
172  (std::ranges::find(eval_data.speedup_headers,
173  *iter_prev_ordered_header) + 1),
174  *run_speedup_iter);
175  }
176  else {
177  //add speedup header to front of evaluation speedup headers if no
178  //previous ordered header in speedups for run
179  eval_data.speedup_headers.insert(
180  eval_data.speedup_headers.cbegin(),
181  *run_speedup_iter);
182  }
183  }
184  }
185  }
186 
187  //return resulting evaluation data
188  return eval_data;
189 }
190 
200 std::vector<std::string> EvaluateAcrossRuns::OrderedRunNames(
201  const EvalAcrossRunsData& eval_data,
202  const std::optional<std::string>& speedup_header) const
203 {
204  //get header to use for speedup ordering of runs
205  //use first speedup in speedup headers if no input speedup header given
206  const std::string speedup_ordering =
207  speedup_header.value_or(eval_data.speedup_headers.front());
208 
209  //declare vector with run names paired with reference wrapper containing
210  //speedup results for run
211  //reference wrapper used to prevent need to copy speedup results
212  std::vector<std::pair<
213  decltype(eval_data.speedup_results_name_to_data)::key_type,
214  std::reference_wrapper<const decltype(eval_data.speedup_results_name_to_data)::mapped_type>>>
215  runs_w_speedup_data;
216 
217  //populate vector of run names paired with reference wrapper containing speedup data
218  std::ranges::transform(
220  std::back_inserter(runs_w_speedup_data),
221  [](const auto& run_name_w_speedup) -> decltype(runs_w_speedup_data)::value_type {
222  return {run_name_w_speedup.first, std::cref(run_name_w_speedup.second)};
223  });
224 
225  //remove runs where current speedup ordering header does not have data or
226  //data is blank (currently assumed that data is a valid float)
227  std::erase_if(
228  runs_w_speedup_data,
229  [&speedup_ordering](const auto& run_name_w_speedup) {
230  if (!(run_name_w_speedup.second.get().contains(speedup_ordering))) {
231  return true;
232  }
233  else {
234  return std::ranges::all_of(
235  run_name_w_speedup.second.get().at(speedup_ordering).at(0),
236  [](unsigned char c) { return isspace(c); });
237  }
238  });
239 
240  //sort runs from greatest speedup to lowest speedup according to speedup
241  //header
242  std::ranges::sort(runs_w_speedup_data, std::ranges::greater{},
243  [&speedup_ordering](const auto& run_w_speedups) {
244  return std::stof(run_w_speedups.second.get().at(speedup_ordering).at(0));
245  });
246 
247  //generate ordered run names from greatest speedup to least speedup
248  //according to speedup header
249  std::vector<std::string> ordered_run_names;
250  std::ranges::transform(
251  runs_w_speedup_data,
252  std::back_inserter(ordered_run_names),
253  [](const auto& run_name_speedup) { return run_name_speedup.first; });
254 
255  //return run names ordered from greatest speedup to least speedup
256  return ordered_run_names;
257 
258  //write each evaluation run name and save order of runs with speedup
259  //corresponding to first speedup header
260  //order of run names is in speedup from largest to smallest
261  /*auto cmp_speedup =
262  [](const std::pair<std::string, float>& a,
263  const std::pair<std::string, float>& b)
264  { return a.second > b.second; };
265  std::set<std::pair<std::string, float>, decltype(cmp_speedup)>
266  run_names_in_order_w_speedup;
267 
268  //generate and add pairs of run name with corresponding "ordering" speedup
269  //for each run to set to get sorted order of runs from fastest to slowest
270  //based on "ordering" speedup
271  for (const auto& [run_name, speedup_results] :
272  eval_data.speedup_results_name_to_data)
273  {
274  run_names_in_order_w_speedup.insert(
275  {run_name,
276  speedup_results.contains(speedup_ordering) ?
277  std::stof(std::string(speedup_results.at(speedup_ordering).at(0))) :
278  0});
279  }*
280 
281  //generate ordered run names from fastest to slowest
282  std::vector<std::string> ordered_run_names;
283  std::transform(
284  run_names_in_order_w_speedup.cbegin(),
285  run_names_in_order_w_speedup.cend(),
286  std::back_inserter(ordered_run_names),
287  [](const auto& run_name_speedup) { return run_name_speedup.first; });
288 
289  return ordered_run_names;*/
290 }
291 
292 //write output evaluation across runs to file
293 void EvaluateAcrossRuns::WriteEvalAcrossRunsToFile(
294  const std::filesystem::path& imp_results_file_path,
295  const std::vector<std::string>& eval_across_runs_top_text,
296  const std::vector<std::string>& eval_across_runs_in_params_show,
297  const EvalAcrossRunsData& eval_data,
298  const std::vector<std::string>& run_names_ordered) const
299 {
300  //write results across architectures to output file
301  //file path for evaluation across runs
302  const std::filesystem::path results_across_run_fp =
303  imp_results_file_path /
304  (std::string(run_eval::kEvalAcrossRunsFileName) +
305  std::string(run_eval::kCsvFileExtension));
306 
307  //initialize output stream for file showing evaluation across runs
308  std::ofstream eval_results_across_run_str(results_across_run_fp);
309 
310  //add text to display on top of results across architecture comparison file
311  for (const auto& comp_file_top_text_line : eval_across_runs_top_text) {
312  eval_results_across_run_str << comp_file_top_text_line << std::endl;
313  }
314  eval_results_across_run_str << std::endl;
315 
316  //write out the name of each input parameter to be displayed
317  for (const auto& input_param_disp_header : eval_across_runs_in_params_show) {
318  eval_results_across_run_str << input_param_disp_header << ',';
319  }
320 
321  //write all the run names in order from fastest to slowest
322  for (const auto& run_name : run_names_ordered) {
323  eval_results_across_run_str << run_name << ',';
324  }
325  eval_results_across_run_str << std::endl;
326 
327  //write evaluation stereo set info, bp parameters, and total runtime for
328  //optimized bp implementation across each run in the evaluation
329  for (const auto& [input_sig, params_display_ordered] :
331  {
332  for (const auto& param_val_disp : params_display_ordered) {
333  eval_results_across_run_str << param_val_disp << ',';
334  }
335  for (const auto& run_name : run_names_ordered)
336  {
337  if (eval_data.input_to_runtime_across_archs.at(
338  run_name).contains(input_sig))
339  {
340  eval_results_across_run_str <<
341  eval_data.input_to_runtime_across_archs.at(run_name).at(input_sig);
342  }
343  eval_results_across_run_str << ',';
344  }
345  eval_results_across_run_str << std::endl;
346  }
347  eval_results_across_run_str << std::endl;
348 
349  //write average speedup results for each run that correspond to a number of
350  //different evaluations of runtimes compared to a baseline
351  eval_results_across_run_str << "Average Speedups" << std::endl;
352  for (const auto& speedup_header : eval_data.speedup_headers) {
353  //don't process if header is empty
354  if (!(speedup_header.empty())) {
355  eval_results_across_run_str << speedup_header << ',';
356  //add empty cell for each input parameter after the first that's
357  //displayed so speedup shown in the same column same line as runtime
358  for (size_t i = 1; i < eval_across_runs_in_params_show.size(); i++) {
359  eval_results_across_run_str << ',';
360  }
361  //write speedup for each run in separate cells in horizontal direction
362  //where each column corresponds to a different evaluation run
363  for (const auto& run_name : run_names_ordered) {
364  if (eval_data.speedup_results_name_to_data.at(run_name).contains(
365  speedup_header))
366  {
367  eval_results_across_run_str <<
368  eval_data.speedup_results_name_to_data.at(run_name).at(
369  speedup_header).at(0) << ',';
370  }
371  else {
372  eval_results_across_run_str << ',';
373  }
374  }
375  //continue to next row of table to write data for next speedup result
376  eval_results_across_run_str << std::endl;
377  }
378  }
379 
380  //go through each speedup and display runs ordered from highest speedup to lowest
381  //speedup
382  eval_results_across_run_str << std::endl;
383  eval_results_across_run_str << "Runs ordered by speedup (highest to lowest)" << std::endl;
384  for (const auto& speedup_header : eval_data.speedup_headers) {
385  //don't process if header is empty
386  if (!(speedup_header.empty())) {
387  eval_results_across_run_str << speedup_header << ',';
388  //get runs ordered by speedup
389  const auto ordered_runs_speedup =
390  OrderedRunNames(
391  eval_data,
392  speedup_header);
393  for (const auto& run_name : ordered_runs_speedup) {
394  const auto& speedup =
395  eval_data.speedup_results_name_to_data.at(run_name).at(
396  speedup_header).at(0);
397  eval_results_across_run_str << run_name << " - " << std::setprecision(3)
398  << std::fixed << std::stof(speedup) << ',';
399  }
400  }
401  //continue to next row of table to write data for next speedup result
402  eval_results_across_run_str << std::endl;
403  }
404 
405  //write location of evaluation results across runs to output console
406  std::cout << "Evaluation of results across all runs in "
407  << results_across_run_fp << std::endl;
408 }
409 
410 //function to get names of runs with results from implementation results
411 //file path
412 std::vector<std::string> EvaluateAcrossRuns::GetRunNames(
413  const std::filesystem::path& imp_results_file_path) const
414 {
415  //iterate through all run results files
416  //create directory iterator with path to run results files
417  std::filesystem::directory_iterator results_files_iter =
418  std::filesystem::directory_iterator(
419  imp_results_file_path / run_eval::kImpResultsRunDataFolderName);
420  std::vector<std::string> run_names;
421  for (const auto& results_fp : results_files_iter)
422  {
423  //get run name from run results file name
424  //and add to vector of run names
425  std::string file_name_no_ext = results_fp.path().stem();
426  if (file_name_no_ext.ends_with(
427  "_" + std::string(run_eval::kRunResultsDescFileName)))
428  {
429  const std::string run_name =
430  file_name_no_ext.substr(
431  0,
432  file_name_no_ext.find(
433  "_" + std::string(run_eval::kRunResultsDescFileName)));
434  run_names.push_back(run_name);
435  }
436  }
437 
438  //remove run name from runs to evaluate across runs if no speedup data
439  //example where this could be the case is baseline data that is used for
440  //comparison with other runs
441  for (auto run_names_iter = run_names.cbegin();
442  run_names_iter != run_names.cend();)
443  {
444  const std::filesystem::path run_speedup_fp =
445  imp_results_file_path / run_eval::kImpResultsSpeedupsFolderName /
446  (std::string(*run_names_iter) + '_' +
447  std::string(run_eval::kSpeedupsDescFileName) +
448  std::string(run_eval::kCsvFileExtension));
449  //remove run from evaluation if no valid speedup data file that corresponds
450  //to run results data file
451  if ((!(std::filesystem::exists(run_speedup_fp))) ||
452  (!(std::filesystem::is_regular_file(run_speedup_fp))))
453  {
454  run_names_iter =
455  run_names.erase(std::ranges::find(run_names, *run_names_iter));
456  }
457  else {
458  run_names_iter++;
459  }
460  }
461 
462  return run_names;
463 }
Declares class with operator function to evaluate implementation runs across multiple architectures....
Declares class for defining input signature for evaluation run that consists of evaluation set number...
void operator()(const std::filesystem::path &imp_results_file_path, const std::vector< std::string > &eval_across_runs_top_text, const std::vector< std::string > &eval_across_runs_in_params_show) const
Evaluate all runs with results in specified file path and generate csv file with evaluation of result...
Class to load and store run results data including speedups from evaluation.
constexpr std::string_view kImpResultsRunDataFolderName
constexpr std::string_view kEvalAcrossRunsFileName
constexpr std::string_view kCsvFileExtension
constexpr std::string_view kSpeedupsDescFileName
constexpr std::string_view kRunResultsDescFileName
constexpr std::string_view kOptimizedRuntimeHeader
constexpr std::string_view kImpResultsSpeedupsFolderName
Structure to store data with mappings for evaluating results across runs as well as speedup headers i...
std::map< std::string, std::map< InputSignature, std::string > > input_to_runtime_across_archs
std::map< std::string, std::map< std::string, std::vector< std::string > > > speedup_results_name_to_data
std::map< InputSignature, std::vector< std::string > > inputs_to_params_disp_ordered
std::vector< std::string > speedup_headers