简易 Timer

我打算积累一些算法。不过刚一动手就遇到了大麻烦,在 Windows 上配置一个简单的 Benchmark 似乎有点困难。Google Benchmark 我观察了半天也没研究明白怎么安,于是想了想,不如自己写一个简易 Timer,作为一切的起点!

Timer 的功能

  • 首先,Timer 有开始计时和终止计时的功能。一个 Timer 可以多次计时,并支持取平均数。以后我可能加上点极值啊回归啊什么的(无限期鸽鸽了)。
  • 其次,Timer 应该可以打包计时,就是一次计时中做多次运算。这是因为有的运算需要的时间非常少,所以需要测多次再平均才能准一点。
  • 最后,还要能生成一个 string report,方便输出。

Timer 的声明

有了上面的想法,可以简单设计出一个 Timer 类:

// seideun/include/auxiliary/Timer.hpp
//
// Created by Deuchie on 2020/8/14.

#ifndef SEIDEUN_TIMER_HPP
#define SEIDEUN_TIMER_HPP

#include <string>
#include <chrono>

namespace seideun::auxiliary {

/** A simple timer
 *
 * # Functionalities
 *
 * - count time with high resolution, generate report in nanoseconds
 * - count the same task multiple times to get average time cost
 * - bulk-count low cost tasks to get higher accuracy and more simplicity
 * - convenient well-formatted report
 */
class Timer {
public:
  Timer();
  explicit Timer(std::string task_name);
  void start();
  void stop(uint32_t repetitions_taken = 1);
  auto get_latest_duration() -> std::chrono::nanoseconds;
  auto get_average_duration() -> std::chrono::nanoseconds;
  auto report() -> std::string;
  void clear(); // clear all saved records

private:
  std::string const task_name_ = "Anonymous Task";
  std::chrono::nanoseconds average_duration_{};
  std::chrono::nanoseconds latest_duration_{};
  std::chrono::time_point<std::chrono::high_resolution_clock> start_time_{};
  uint32_t total_repetitions_ = 0;
};

}

#endif //SEIDEUN_TIMER_HPP

Timer 的实现

// seideun/src/auxiliary/Timer.cpp
//
// Created by Deuchie on 2020/8/14.

#include "auxiliary/Timer.hpp"

#include "fmt/format.h"

void seideun::auxiliary::Timer::start() {
  start_time_ = std::chrono::high_resolution_clock::now();
}

seideun::auxiliary::Timer::Timer(std::string task_name)
  : task_name_(std::move(task_name)) {}

void seideun::auxiliary::Timer::stop(uint32_t repetitions_taken) {
  auto this_duration = std::chrono::high_resolution_clock::now() - start_time_;
  auto new_total_repetitions = total_repetitions_ + repetitions_taken;
  latest_duration_ = this_duration / repetitions_taken;
  average_duration_ = (total_repetitions_ * average_duration_ + this_duration) / new_total_repetitions;
  total_repetitions_ = new_total_repetitions;
}

auto seideun::auxiliary::Timer::get_latest_duration() -> std::chrono::nanoseconds {
  return latest_duration_;
}

auto seideun::auxiliary::Timer::get_average_duration() -> std::chrono::nanoseconds {
  return average_duration_;
}

auto seideun::auxiliary::Timer::report() -> std::string {
  char static constexpr format_string[] = R"({}:
    total repetition times: {}
    average duration: {} ns)";
  return fmt::format(format_string, task_name_, total_repetitions_, average_duration_.count());
}

void seideun::auxiliary::Timer::clear() {
  total_repetitions_ = 0;
}

seideun::auxiliary::Timer::Timer() = default;

测试 Timer

这里我没有用 Google Test,就简单写了一个 Demo 看看输出正不正常即可

#include "auxiliary/Timer.hpp"
#include "auxiliary/primitive_aliases.hpp"
#include "fmt/format.h"

#include <fstream>

int main() {
  std::ofstream out("temp.txt"); // I used this to prevent compiler optimization
  seideun::auxiliary::Timer timer;

  timer.start();
  for (u32 i = 0; i != 10000; ++i) {
    out << i * 32 - 7 << ' ';
  }
  timer.stop(10000);

  fmt::print("# Latest duration: {} ns\n# Report:\n{}\n", timer.get_latest_duration().count(), timer.report());

  // Let's try another cycle
  timer.start();
  for (u32 i = 0; i != 5000; ++i) {
    out << i * i * i + 41976283 << ' ';
  }
  timer.stop(5000);

  puts("\nAnother Test:");
  fmt::print("# Latest duration: {} ns\n# Report:\n{}\n", timer.get_latest_duration().count(), timer.report());
}

输出是这样的:

>>> ./seideun.exe
# Latest duration: 313 ns
# Report:
Anonymous Task:
    total repetition times: 10000
    average duration: 313 ns

Another Test:
# Latest duration: 367 ns
# Report:
Anonymous Task:
    total repetition times: 15000
    average duration: 331 ns

Process finished with exit code 0

PS: primitive_alias 里的声明如下:

using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;

我个人觉得这个写起来方便,而且定长好处多多啊朋友们!

posted @ 2020-08-14 05:35  seideun  阅读(187)  评论(0)    收藏  举报