1. 程式人生 > 實用技巧 >演算法(一)簡易 Timer

演算法(一)簡易 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;

我個人覺得這個寫起來方便,而且定長好處多多啊朋友們!