Stavo testando la velocità del loop di diversi modi su un vettore std ::. Nel seguente codice, ritengo 5 modi per calcolare la somma di tutti gli elementi di un vettore di N = 10000000 elementi:Gli algoritmi STL sono ottimizzati per la velocità?
- utilizzando iteratori
- usando indici interi
- usando indici interi, svolgimento di un fattore 2
- usando indici interi, srotolando di un fattore 4
- utilizzando std :: accumulare
Il co de è compilato con g ++ per Windows, la riga di comando utilizzato per compilare è:
g++ -std=c++11 -O3 loop.cpp -o loop.exe
Ho eseguito il codice di 4 volte, misurando il tempo di ciascun metodo, ottengo i seguenti risultati (tempo in microsecondi, max e min sono indicati):
- iteratori: 8002 - 8007
- indici Int: 8004 - 9003
- srotolare 2: 6004 - 7005
- unroll 4: 4001 - 5004
- accumulano: 8005 - 9007
Quello che questi esperimenti sembrano indicare è:
Looping con iteratori vs indici interi non fa molta differenza, almeno con piena ottimizzazione.
apertolo il ciclo paga off
Sorprendentemente, lo STL :: accumulano dà la performance peggiore.
Mentre le conclusioni 1 e 2 erano previste, il numero 3 è piuttosto sorprendente. Non tutti i libri dicono di usare gli algoritmi STL invece di scrivere loop da solo?
Sto commettendo errori nel modo in cui sto misurando il tempo o nel modo in cui interpreto i risultati? Avete uno scenario diverso nel caso in cui proviate il codice indicato di seguito?
#include <iostream>
#include <chrono>
#include <vector>
#include <numeric>
using namespace std;
using namespace std::chrono;
int main()
{
const int N = 10000000;
vector<int> v(N);
for (int i = 0; i<N; ++i)
v[i] = i;
//looping with iterators
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
long long int sum = 0;
for (auto it = v.begin(); it != v.end(); ++it)
sum+=*it;
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
cout << duration << "microseconds output = " << sum << " (Iterators)\n";
}
//looping with integers
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
long long int sum = 0;
for (int i = 0; i<N; ++i)
sum+=v[i];
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
cout << duration << "microseconds output = " << sum << " (integer index)\n";
}
//looping with integers (UNROLL 2)
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
long long int sum = 0;
for (int i = 0; i<N; i+=2)
sum+=v[i]+v[i+1];
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
cout << duration << "microseconds output = " << sum << " (integer index, UNROLL 2)\n";
}
//looping with integers (UNROLL 4)
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
long long int sum = 0;
for (int i = 0; i<N; i+=4)
sum+=v[i]+v[i+1]+v[i+2]+v[i+3];
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
cout << duration << "microseconds output = " << sum << " (integer index, UNROLL 4)\n";
}
//using std::accumulate
{
high_resolution_clock::time_point t1 = high_resolution_clock::now();
long long int sum = accumulate(v.begin(), v.end(), static_cast<long long int>(0));
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
cout << duration << "microseconds output = " << sum << " (std::accumulate)\n";
}
return 0;
}
Questa roba funziona abbastanza rapidamente, puoi eseguire 50 iterazioni e quindi fornire media e deviazione standard per ogni metodo? Possiamo fare un test di signficance. – AndyG
L'esecuzione di un test 4 volte non è sufficiente per ottenere un benchmark sufficientemente accurato per gli algoritmi di temporizzazione. Dovresti prendere la media di 1000 di campioni. Mi aspetterei che 'std :: accumulate' abbia prestazioni all'incirca uguali all'esempio iteratore, dal momento che sembra essere l'implementazione di riferimento di esso. I tuoi esempi di srotolamento sono intelligenti poiché salvano le iterazioni del ciclo, ma funzionano perché tu sai informazioni sulla cosa che stai cercando di accumulare. Nel caso generale non sarebbe possibile sapere se posso srotolare e 'std :: accumulate' deve gestire il caso generale. – aruisdante
Penso che questo dica di più sull'ottimizzatore del compilatore rispetto a 'std :: accumulate'. I miei compilatori (clang 3.5 e gcc 4.9.2) offrono lo stesso tempo di esecuzione per iteratori, indici interi e 'std :: accumulate' (e lo srotolamento fa una piccola, piccola differenza). – Cornstalks