Testowałem prędkość różnych sposobów pętli na std :: vector. W poniższym kodu, pod 5 sposoby obliczania sumy wszystkich elementów wektora n = 10000000 elementów:Czy algorytmy STL są zoptymalizowane pod kątem szybkości?
- wykorzystaniem iteratorami
- pomocą indeksów całkowite
- za pomocą indeksów całkowitych, rozwijając poprzez: a czynnik 2
- pomocą indeksów całkowitych, rozwijając przez współczynnik 4
- pomocą std :: gromadzić
The Co de jest skompilowany z g ++ dla Windows, wiersz poleceń używany do kompilacji jest:
g++ -std=c++11 -O3 loop.cpp -o loop.exe
wpadłem kod 4 razy, mierząc czas każdej metody, mam następujące wyniki (czas w mikrosekundach, max i min podano)
- Iterators: 8002 - 8007
- indeksy Int: 8004 - 9003
- odwijać 2: 6004 - 7005
- Unroll 4: 4001 - 5004
- gromadzić: 8005 - 9007
Co te eksperymenty wydają się wskazywać na to:
Pętla z iteratorów vs indeksów całkowitych nie robi dużej różnicy, przynajmniej z pełną optymalizacją.
rozwinięciem pętli opłaca
Niespodziewanie, stl :: Akumuluj daje gorszą wydajność.
Podczas gdy wnioski 1 i 2 były spodziewane, liczba 3 jest dość zaskakująca. Czy nie wszystkie książki mówią, że używają algorytmów STL zamiast pisać pętle sam?
Czy popełniam jakiś błąd w sposobie mierzenia czasu lub sposobu, w jaki interpretuję wyniki? Czy macie inny scenariusz na wypadek wypróbowania kodu podanego poniżej?
#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;
}
To działa dość szybko, można uruchomić na 50 iteracji, a następnie zapewnić średnie i standardowe odchylenie dla każdej metody? Możemy zrobić test trafności. – AndyG
Tylko czterokrotne wykonanie testu nie jest wystarczające, aby uzyskać całkiem rozsądny punkt odniesienia dla algorytmów pomiaru czasu. Powinieneś pobrać średnio 1000 próbek. Spodziewam się, że 'std :: accumulate' będzie miał wydajność mniej więcej równą przykładowi iteratora, ponieważ wydaje się, że jest to jego referencyjna implementacja. Twoje rozwijane przykłady są inteligentne, ponieważ zapisują iteracje w pętli, ale działają, ponieważ znasz informacje o rzeczy, którą próbujesz zgromadzić. W ogólnym przypadku nie można by się dowiedzieć, czy mogę rozwinąć, a 'std :: accumulate' musi obsłużyć ogólny przypadek. – aruisdante
Myślę, że mówi to więcej o optymalizatorze twojego kompilatora niż o 'std :: accumulate'. Moje kompilatory (clang 3.5 i gcc 4.9.2) dają mniej więcej ten sam czas działania dla iteratorów, indeksów całkowitych i 'std :: accumulate' (a rozwijanie tworzy niewielką, drobną różnicę). – Cornstalks