I wrote a timing function that records the run time of a function and calculates the mean and the standard deviation over multiple runs. I was surprised to find very high standard deviations, even for seemingly simple tasks such as adding two doubles.
I analysed the data in python (see the plots). The c++ output was 19.6171 ns +/- 21.9653ns (82799807 runs)
when compiled with:
gcc version 8.3.0 (Debian 8.3.0-19)
/usr/bin/c++ -O3 -DNDEBUG -std=gnu++17
The whole test was done on my personal computer, which was not idle but running a DE, a browser, my IDE and other processes. There was free RAM available during the test though. My double core CPU with HT was idling below 10% usage.
Is a spike from an average value of 20 ns to 50 µs to be expected for this situation?
Plot of run times
This is the content of std::vector<double> run_times
. I don't see any pattern.
Histogram of run times
Note log y axis (number of samples in this bin).
timing.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()};
}
timing.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)";
}
main.cpp
#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;
}