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 ms

Check out similar posts by category: C/C++, Performance