В этом фрагменте кода я сравниваю производительность двух функционально идентичных циклов:
for (int i = 1; i < v.size()-1; ++i) {
int a = v[i-1];
int b = v[i];
int c = v[i+1];
if (a < b && b < c)
++n;
}
и
for (int i = 1; i < v.size()-1; ++i)
if (v[i-1] < v[i] && v[i] < v[i+1])
++n;
Первый работает значительно медленнее, чем второй, на множестве разных компиляторов С++ с флагом оптимизации, установленным на O2
:
- второй цикл составляет около 330% медленнее теперь с Clang 3.7.0
- второй цикл примерно на 2% медленнее с gcc 4.9.3
- второй цикл примерно на 2% медленнее с Visual С++ 2015
Я озадачен тем, что современные оптимизаторы С++ имеют проблемы с обработкой этого случая. Какие-нибудь подсказки почему? Должен ли я писать уродливый код без использования временных переменных, чтобы получить максимальную производительность?
Использование временных переменных делает код быстрее, а иногда и резко. Что происходит?
Полный код, который я использую, приведен ниже:
#include <algorithm>
#include <chrono>
#include <random>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
using namespace std::chrono;
vector<int> v(1'000'000);
int f0()
{
int n = 0;
for (int i = 1; i < v.size()-1; ++i) {
int a = v[i-1];
int b = v[i];
int c = v[i+1];
if (a < b && b < c)
++n;
}
return n;
}
int f1()
{
int n = 0;
for (int i = 1; i < v.size()-1; ++i)
if (v[i-1] < v[i] && v[i] < v[i+1])
++n;
return n;
}
int main()
{
auto benchmark = [](int (*f)()) {
const int N = 100;
volatile long long result = 0;
vector<long long> timings(N);
for (int i = 0; i < N; ++i) {
auto t0 = high_resolution_clock::now();
result += f();
auto t1 = high_resolution_clock::now();
timings[i] = duration_cast<nanoseconds>(t1-t0).count();
}
sort(timings.begin(), timings.end());
cout << fixed << setprecision(6) << timings.front()/1'000'000.0 << "ms min\n";
cout << timings[timings.size()/2]/1'000'000.0 << "ms median\n" << "Result: " << result/N << "\n\n";
};
mt19937 generator (31415); // deterministic seed
uniform_int_distribution<> distribution(0, 1023);
for (auto& e: v)
e = distribution(generator);
benchmark(f0);
benchmark(f1);
cout << "\ndone\n";
return 0;
}