首页 > 解决方案 > 为什么添加两个值在运行时会有如此高的可变性?

问题描述

我编写了一个计时函数,它记录函数的运行时间并计算多次运行的平均值和标准差。我惊讶地发现标准偏差非常高,即使是看似简单的任务,例如添加两个双打。我在 python 中分析了数据(见图表)。c++ 输出是19.6171 ns +/- 21.9653ns (82799807 runs)在编译时使用:

gcc version 8.3.0 (Debian 8.3.0-19)
/usr/bin/c++ -O3 -DNDEBUG -std=gnu++17

整个测试是在我的个人电脑上完成的,这台电脑并没有闲置,而是运行着一个 DE、一个浏览器、我的 IDE 和其他进程。不过,在测试期间有可用的 RAM。我的带 HT 的双核 CPU 的空闲使用率低于 10%。
在这种情况下,是否会出现从平均值 20 ns 到 50 µs 的尖峰?

运行时间图
这是 的内容std::vector<double> run_times。我没有看到任何模式。 运行时图结束

运行时间的直方图
注意 log y 轴(此 bin 中的样本数)。 运行时间直方图

计时.h

#include <cstdint>
#include <ostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <chrono>
#include <numeric>
#include <fstream>


struct TimingResults{
    // all time results are in nanoseconds
    double mean;
    double standard_deviation;
    uint64_t number_of_runs;
};


std::ostream& operator<<(std::ostream& os, const TimingResults& results);


template <typename InputIterator>
std::pair<typename InputIterator::value_type, typename InputIterator::value_type> 
calculate_mean_and_standard_deviation(InputIterator first, InputIterator last){
    double mean = std::accumulate(first, last, 0.) / std::distance(first, last);
    double sum = 0;
    std::for_each(first, last, [&](double x){sum += (x - mean) * (x - mean);});
    return {mean, std::sqrt(sum / (std::distance(first, last) - 1))};
}


template<uint64_t RunTimeMilliSeconds = 4000, typename F, typename... Args>
TimingResults measure_runtime(F func, Args&&... args){
    std::vector<double> runtimes;
    std::chrono::system_clock::time_point b;
    auto start_time = std::chrono::high_resolution_clock::now();
    do {
        auto a = std::chrono::high_resolution_clock::now();
        func(std::forward<Args>(args)...);
        b = std::chrono::high_resolution_clock::now();
        runtimes.push_back(std::chrono::duration_cast<std::chrono::nanoseconds>(b - a).count());
    } while (std::chrono::duration_cast<std::chrono::milliseconds>(b-start_time).count() <= RunTimeMilliSeconds);
    auto [mean, std_deviation] = calculate_mean_and_standard_deviation(runtimes.begin(), runtimes.end());
    return {mean, std_deviation, runtimes.size()};
}

计时.cpp

#include <iostream>
#include "timing.h"


std::ostream& operator<<(std::ostream& os, const TimingResults& results){
    return os << results.mean << " ns" << " +/- " << results.standard_deviation << "ns ("
    << results.number_of_runs << " runs)";
}

主文件

#include "src/timing/timing.h"
#include <iostream>


int main(){
    auto res = measure_runtime([](double x, double y){return x * y;}, 6.9, 9.6);
    std::cout << res;
}

标签: c++benchmarkingtiming

解决方案


现代 CPU 很容易以几个 10^9 FLOPS 的顺序执行,即一次操作的预期时间低于 1 ns。然而,这指的是峰值性能。对于大多数现实世界的工作负载,由于内存和缓存的影响,性能会低得多。

您的基准测试的问题是您正在计时单个操作a获取时间点的开销b很可能只是超过了您实际尝试测量的时间。此外,evenstd::chrono::high_resolution_clock不会为您提供皮秒精度(尽管这在原则上是实现并且取决于硬件)。显而易见的解决方法是执行操作N次数,时间为 ,然后将总时间除以N。在某些时候,您会看到您的结果变得一致。(随时发布您的结果。)

TL;DR:您正试图用怀表计时。


推荐阅读