AutoBenchmark:使用 std::chrono 在 C++ 中进行自动多间隔基准测试
问题:
你的 C++ 代码的某些部分存在性能问题。你正在寻找一个轻量级解决方案,允许你轻松记录不同的时间点并自适应打印结果(即你不想知道某事运行了 1102564643 纳秒,你只想知道它花了 1.102 秒)
解决方案
我编写了 AutoBenchmark,以便你可以为你的微基准测试需求获得最省事的 C++11 体验。
AutoBenchmark 允许你记录不同的时间点,每个都有标签。第一个时间点在此实例构造时记录。AutoBenchmark 支持任意数量的时间点。
当此类的实例被析构时,它会自动打印所有基准测试结果,但仅当自构造以来已过可配置的时间量 - 这非常方便,特别是如果你的函数有多个退出点,否则需要多次调用 Print()。
它允许你在达到某些性能目标时忽略基准测试(例如,如果你有一个仅对某些数据点慢的 for 循环,你可以配置 AutoBenchmark 仅打印慢运行的 infos)。
默认行为(即使用默认参数的构造函数)是禁用自动打印 - 在这种情况下,你可以自己调用 Print()。
头文件(AutoBenchmark.hpp):
AutoBenchmark.hpp
/**
* AutoBenchmark v1.1
* Written by Uli Köhler
* https://techoverflow.net
*
* https://techoverflow.net/2018/03/31/autobenchmark-automatic-multi-interval-benchmarking-in-c-using-stdchrono/
*
* Published under CC0 1.0 Universal
*/
#pragma once
#include <chrono>
#include <string>
#include <limits>
#include <vector>
using namespace std;
/**
* Automatic benchmark: Allows you to record different points in time,
* each with a label. The first time point is recorded when this instance
* is constructed. This class supports an arbitrary number of time points.
*
* When an instance of this class is destructed, it will automatically
* print all the benchmark results, but only if a configurable amount
* of time has passed since its construction, allowing you to automatically
*/
class AutoBenchmark {
public:
/**
* Initialize a benchmark that automatically prints its records
* on destruction if the total time consumed is >= autoPrintThreshold
* at the time of destruction. Only the time up until the last Record()ed
* label is printed.
* @param autoPrintThreshold: How many seconds will need to have passed
* so that the destructor will automatically print. Default is to never print.
* @param benchmarkLabel: A label that will be printed once, before all the results
* @param lineLabel: A label (e.g. indent) that will be printed before each result line
*/
AutoBenchmark(double autoPrintThreshold=std::numeric_limits<double>::max(), const std::string& benchmarkLabel = "", const std::string& lineLabel = " ");
~AutoBenchmark();
/**
* Record a datapoint
*/
void Record(const std::string& label = "");
void Record(const char *label = "");
/**
* Print all time deltas
*/
void Print();
/**
* Reset the benchmark, as if it were a new instance.
*/
void Reset();
/**
* Return now() - first timepoint in seconds.
*/
double TotalSeconds();
private:
vector<std::chrono::system_clock::time_point> times;
vector<std::string> labels;
double autoPrintThreshold;
std::string benchmarkLabel;
std::string lineLabel;
};源文件(AutoBenchmark.cpp):
AutoBenchmark.cpp
#include "AutoBenchmark.hpp"
#include <iostream>
AutoBenchmark::AutoBenchmark(double autoPrintThreshold, const std::string& benchmarkLabel, const std::string& lineLabel)
: autoPrintThreshold(autoPrintThreshold), benchmarkLabel(benchmarkLabel), lineLabel(lineLabel) {
times.push_back(chrono::system_clock::now());
labels.emplace_back("Begin"); // Just to keep indices the same
}
AutoBenchmark::~AutoBenchmark() {
if(TotalSeconds() >= autoPrintThreshold) {
Print();
}
}
void AutoBenchmark::Record(const std::string &label) {
times.push_back(chrono::system_clock::now());
labels.emplace_back(label); // Just to keep indices the same
}
void AutoBenchmark::Record(const char *label) {
Record(string(label));
}
void AutoBenchmark::Print() {
if(benchmarkLabel.length()) {
cout << benchmarkLabel << '\n';
}
for (size_t i = 1; i < times.size(); i++) {
// Compute time interval for size comparison
chrono::duration<double, std::nano> ns = times[i] - times[i - 1];
chrono::duration<double, std::micro> us = times[i] - times[i - 1];
chrono::duration<double, std::milli> ms = times[i] - times[i - 1];
chrono::duration<double> s = times[i] - times[i - 1];
chrono::duration<double, std::ratio<60>> min = times[i] - times[i - 1];
chrono::duration<double, std::ratio<3600>> hrs = times[i] - times[i - 1];
// Print
if(ns.count() < 1000.0) {
cout << lineLabel << labels[i] << " took " << ns.count() << " ns\n";
} else if(us.count() < 1000.0) {
cout << lineLabel << labels[i] << " took " << us.count() << " μs\n";
} else if (ms.count() < 1000.0) {
cout << lineLabel << labels[i] << " took " << ms.count() << " ms\n";
} else if (s.count() < 60.0) {
cout << lineLabel << labels[i] << " took " << s.count() << " seconds\n";
} else if (min.count() < 1000.0) {
cout << lineLabel << labels[i] << " took " << min.count() << " minutes\n";
} else {
cout << lineLabel << labels[i] << " took " << hrs.count() << " hours\n";
}
}
cout << flush;
}
void AutoBenchmark::Reset() {
times.clear();
labels.clear();
times.push_back(chrono::system_clock::now());
labels.emplace_back("Begin");
}
double AutoBenchmark::TotalSeconds() {
chrono::duration s = chrono::system_clock::now() - times[0];
return s.count();
}用法示例:
AutoBenchmark_usage.cpp
#include "AutoBenchmark.hpp"
void MySlowFunction() {
// 每次运行耗时 >= 0.3 秒将自动打印
AutoBenchmark myBenchmark(0.3, "Results of running MySlowFunction():");
// .. 执行任务 1 ...
myBenchmark.Record("Running task 1"); // 将打印为:Running task 1 took 1.2ms
// .. 执行任务 2 ...
myBenchmark.Record("Running task 2");
// 循环示例
for(size_t i = 0; ....) {
// ... 在此执行循环迭代任务 ...
myBenchmark.Record("Loop iteration " + std::to_string(i));
}
// myBenchmark 将在此析构,所以如果 MySlowFunction() 花了
// 超过 0.3 秒运行直到返回,结果将被打印
// 到 cout 自动。
}如果 MySlowFunction() 总体运行超过 0.3 秒,AutoBenchmark 将在析构时打印结果 - 即当 MySlowFunction() 返回时:
autobenchmark_output.txt
Results of running MySlowFunction():
Running task 1 took 260.826 ms
Running task 2 took 36.148 μs
Loop iteration 0 took 2.5522 seconds
Loop iteration 1 took 664.059 ms
Loop iteration 2 took 22.2772 ms
Loop iteration 3 took 57.4024 ms
Loop iteration 4 took 16.9928 ms
Loop iteration 5 took 14.0497 ms
Loop iteration 6 took 62.5218 msCheck out similar posts by category:
C/C++, Performance
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow