benchmark_runner.h 5.94 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12

#ifndef BENCHMARK_RUNNER_H
#define BENCHMARK_RUNNER_H

#include <string>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <numeric>
#include <iostream>
#include <fstream>
#include <bits/stdc++.h>
FritzFlorian committed
13 14 15 16 17 18 19 20
#include <thread>
#include <map>

#include <tuple>
#include <unistd.h>
#include <string>
#include <fstream>
#include <sstream>
21 22 23 24 25 26 27 28 29 30 31

using namespace std;

class benchmark_runner {
 private:
  string csv_path_;
  string csv_name_;

  chrono::steady_clock::time_point last_start_time_;
  vector<long> times_;

32 33 34 35 36 37
  bool memory_stats_enabled_{false};
  const string MEMORY_PRE_RUN = "memory_pages_pre_run";
  const string MEMORY_POST_RUN = "memory_pages_post_run";
  unsigned long memory_pre_run_;
  unsigned long memory_post_run_;

FritzFlorian committed
38 39
  map<string, vector<long>> custom_stats_;

40 41 42
  void print_statistics() {
    long time_sum = std::accumulate(times_.begin(), times_.end(), 0l);
    cout << "Average Runtime (us): " << (time_sum / times_.size()) << endl;
FritzFlorian committed
43 44 45 46 47

    for (auto &iter : custom_stats_) {
      long custom_stat_sum = std::accumulate(iter.second.begin(), iter.second.end(), 0l);
      cout << "Average " << iter.first << ": " << (custom_stat_sum / times_.size()) << endl;
    }
48 49 50 51 52 53 54 55
  }

  inline bool file_exists(const std::string &name) {
    ifstream f(name);
    return f.good();
  }

 public:
FritzFlorian committed
56 57 58
  benchmark_runner(string csv_path, string csv_name) : csv_path_{std::move(csv_path)},
                                                       csv_name_{std::move(csv_name)},
                                                       times_{} {
59 60 61 62 63 64
    string command = "mkdir -p " + csv_path_;
    int res = system(command.c_str());
    if (res) {
      cout << "Error while creating directory!" << endl;
      exit(1);
    }
FritzFlorian committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  }

  /**
   * Queries the pages used by the current process.
   * Splits between private and shared pages.
   *
   * @return Tuple(private_pages, shared_pages) used by the process.
   */
  static std::pair<unsigned long, unsigned long> query_process_memory_pages() {
    pid_t my_pid = getpid();
    std::ostringstream proc_stats_path_builder;
    proc_stats_path_builder << "/proc/" << my_pid << "/smaps_rollup";
    std::string proc_stats_path = proc_stats_path_builder.str();

    std::ifstream proc_stats_file{proc_stats_path};

    std::string line;
    unsigned long total_shared = 0;
    unsigned long total_private = 0;
    while (std::getline(proc_stats_file, line)) {
      std::stringstream line_input(line);
      std::string type;
      unsigned long size;
      line_input >> type >> size;

      if (type.find("Shared") != std::string::npos) {
        total_shared += size;
      }
      if (type.find("Private") != std::string::npos) {
        total_private += size;
      }
    }

    return std::make_pair(total_private, total_shared);
  }
100

FritzFlorian committed
101 102
  void add_custom_stats_field(const string name) {
    custom_stats_.insert({name, {}});
103 104
  }

105 106 107 108 109 110
  void enable_memory_stats() {
    memory_stats_enabled_ = true;
    add_custom_stats_field(MEMORY_PRE_RUN);
    add_custom_stats_field(MEMORY_POST_RUN);
  }

111 112 113 114 115 116 117 118 119
  void pre_allocate_stats(size_t num = 100000) {
    times_.reserve(num);
    memset(times_.data(), 'a', num * sizeof(long));
    for (auto &iter : custom_stats_) {
      iter.second.reserve(num);
      memset(iter.second.data(), 'a', num * sizeof(long));
    }
  }

120 121 122 123 124 125 126 127 128 129 130 131 132
  static void read_args(int argc, char **argv, int &num_threads, string &path) {
    if (argc < 3) {
      cout << "Must Specifiy concurrency and output directory! (usage: `benchmark <output_directory> <num_threads>`)"
           << endl;
      exit(1);
    }

    string tmp = argv[1];
    path = tmp;
    num_threads = atoi(argv[2]);
  }

  void start_iteration() {
133 134 135 136 137
    if (memory_stats_enabled_) {
      auto memory_stats = query_process_memory_pages();
      memory_pre_run_ = memory_stats.first;
    }

138 139 140 141
    last_start_time_ = chrono::steady_clock::now();
  }

  void end_iteration() {
142 143
    size_t iteration_index = times_.size();

144 145
    auto end_time = chrono::steady_clock::now();
    long time = chrono::duration_cast<chrono::microseconds>(end_time - last_start_time_).count();
FritzFlorian committed
146

147
    times_.emplace_back(time);
FritzFlorian committed
148 149 150
    for (auto &iter : custom_stats_) {
      iter.second.emplace_back(0);
    }
151 152 153 154 155 156 157
    if (memory_stats_enabled_) {
      auto memory_stats = query_process_memory_pages();
      memory_post_run_ = memory_stats.first;

      custom_stats_[MEMORY_PRE_RUN][iteration_index] = memory_pre_run_;
      custom_stats_[MEMORY_POST_RUN][iteration_index] = memory_post_run_;
    }
FritzFlorian committed
158 159 160 161 162
  }

  void store_custom_stat(const string &name, long value) {
    auto &stat_vector = custom_stats_[name];
    stat_vector[stat_vector.size() - 1] = value;
163 164
  }

165 166 167
  void run_iterations(int count,
                      const function<void(void)> measure,
                      int warmup_count,
FritzFlorian committed
168 169
                      const function<void(void)> prepare = []() {},
                      const function<void(void)> finish = []() {}) {
170
    for (int i = 0; i < warmup_count; i++) {
171 172
      prepare();
      measure();
173 174 175
    }

    for (int i = 0; i < count; i++) {
FritzFlorian committed
176 177
      using namespace std::literals;
      this_thread::sleep_for(100us);
178
      prepare();
179
      start_iteration();
180
      measure();
181
      end_iteration();
FritzFlorian committed
182
      finish();
183 184 185 186 187 188 189 190 191 192 193 194 195
    }
  }

  void commit_results(bool print_stats) {
    if (print_stats) {
      print_statistics();
    }

    string full_filename = csv_path_ + csv_name_;
    bool write_header = !file_exists(full_filename);

    { // Scope for output file
      ofstream o(full_filename, std::fstream::out | std::fstream::app);
FritzFlorian committed
196

197
      if (write_header) {
FritzFlorian committed
198 199 200 201 202
        o << "runtime_us";
        for (auto &iter : custom_stats_) {
          o << ";" << iter.first;
        }
        o << endl;
203
      }
FritzFlorian committed
204 205 206 207 208 209 210 211

      // TODO: make this more efficient
      for (size_t i = 0; i < times_.size(); i++) {
        o << times_[i];
        for (auto &iter : custom_stats_) {
          o << ";" << iter.second[i];
        }
        o << endl;
212 213 214 215
      }
    } // End Scope for output file

    times_.clear();
FritzFlorian committed
216 217 218
    for (auto &iter : custom_stats_) {
      iter.second.clear();
    }
219 220 221 222
  }
};

#endif //BENCHMARK_RUNNER_H