为什么在运行时添加两个值会有如此高的可变性?

Ola*_*laf 2 c++ benchmarking timing

我编写了一个计时函数,记录了函数的运行时间,并计算了多次运行的均值和标准差。我惊讶地发现即使是看似简单的任务(例如加两个双打)也有很高的标准偏差。我分析了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
Run Code Online (Sandbox Code Playgroud)

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

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

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

定时

gcc version 8.3.0 (Debian 8.3.0-19)
/usr/bin/c++ -O3 -DNDEBUG -std=gnu++17
Run Code Online (Sandbox Code Playgroud)

计时文件

#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()};
}
Run Code Online (Sandbox Code Playgroud)

main.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)";
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*ann 7

现代的CPU可以轻松执行大约10 ^ 9 FLOPS的数量级,即,一次操作的预期时间低于1 ns。但是,这指的是峰值性能。对于大多数实际工作负载,由于内存和缓存的影响,性能将大大降低。

基准测试的问题在于您正在安排单个操作的时间。获取时间点的开销ab可能简单地超过你实际上是试图测量的时间。此外,甚至std::chrono::high_resolution_clock不会为您提供皮秒级的精度(尽管从原理上讲,实现方式和硬件有关)。明显的解决方法是执行操作N时间,然后将时间除以N。在某个时候,您会看到结果变得一致。(随时发布您的结果。)

TL; DR:您正在尝试使用怀表计时闪电。