I det här exemplet så funderade jag dels på hur effektiva for-each-loopar är jämfört med vanliga loopar, och dels så funderade jag på hur effektivt min kompilator optimerade i kombination med hur min processor optimerar i sig.
Jag har någon slags stationär dator som är rätt snabb (bra specifikationer va?).
Här är koden.
#include <iostream>
#include <chrono>
#include <string>
#include <random>
using namespace std;
chrono::high_resolution_clock::time_point start;
void tic() {
start = std::chrono::high_resolution_clock::now();
}
double toc() {
auto finish = std::chrono::high_resolution_clock::now();
std::chrono::duration duration(finish- start);
return duration.count();
}
int main(int argc, char **argv) {
vector a(100000000);
vector b;
b.reserve(a.size());
cout << endl;
cout << "generating numbers " << endl;
for (auto &f: a) {
f = (float)(rand()%10000) / 10000. - .5;
}
cout << "random generation finished... numbers like:" << endl;
for (int i = 0 ;i < 10; ++i) {
cout << a[i] << " ";
}
cout << endl;
tic();
for (auto f: a) {
b.push_back(f * (f>0.));
}
cout << "multiplications for each " << toc() << endl;
b.clear();
b.reserve(a.size());
tic();
for (int i = 0; i < a.size(); ++i) {
auto f = a[i];
b.push_back(f*(f > 0));
}
cout << "multiplications regular-for: " << toc() << endl;
b.clear();
b.resize(a.size());
tic();
for (int i = 0; i < a.size(); ++i) {
auto f = a[i];
b[i] = f * (f > 0);
}
cout << "indexes multiplications regular-for: " << toc() << endl;
b.clear();
b.resize(a.size());
tic();
for (int i = 0; i < a.size(); ++i) {
b[i] = ReLu(a[i]);
}
cout << "indexes inline function regular-for: " << toc() << endl;
b.clear();
b.reserve(a.size());
tic();
for (auto f: a) {
if (f > 0) {
b.push_back(f);
}
else {
b.push_back(0);
}
}
cout << "if statements for each " << toc() << endl;
b.clear();
b.reserve(a.size());
tic();
for (int i = 0; i < a.size(); ++i) {
auto f = a[i];
if (f > 0) {
b.push_back(f);
}
else {
b.push_back(0);
}
}
cout << "if-statements regular for " << toc() << endl;
b.clear();
b.resize(a.size());
tic();
for (int i = 0; i < a.size(); ++i) {
auto f = a[i];
if (f > 0) {
b[i] = f;
}
else {
b[i] = 0;
}
}
cout << "indexes if-statements regular for " << toc() << endl;
}
Resultatet blev som följande: Utan några optimeringar givna till g++ får jag följande resultat.
generating numbers
random generation finished... numbers like:
0.4383 -0.4114 -0.2223 0.1915 0.2793 0.3335 0.0386 -0.4508 0.1649 -0.3579
multiplications for each 4.21264
multiplications regular-for: 2.96703
indexes multiplications regular-for: 1.37391
indexes inline function regular-for: 1.43445
indexes inline function regular-for: 1.43445
if statements for each 3.5061
if-statements regular for 3.10215
indexes if-statements regular for 1.64089
Med optimeringar (-O3) får jag följande resultat
generating numbers
random generation finished... numbers like:
0.4383 -0.4114 -0.2223 0.1915 0.2793 0.3335 0.0386 -0.4508 0.1649 -0.3579
multiplications for each 0.27903
multiplications regular-for: 0.158509
indexes multiplications regular-for: 0.15735
indexes inline function regular-for: 0.161381
indexes inline function regular-for: 0.161381
if statements for each 0.572224
if-statements regular for 0.587925
indexes if-statements regular for 0.150071
Så vad kan man dra för slutsatser? Jag tar med mig följande tumregler.
1: Ska det gå snabbt: Se till att använda kompilatorns inbyggda optimeringar. (Det går ungefär tio gånger snabbare i det här exemplet).
2: Det går oftast snabbare att använda en vanlig indexerad for-loop än att använda en for-each-loop (om prestanda är väldigt väldigt viktigt)
3: Egna optimeringar kan fungera, som exempelvis när man som ovan multiplicerar med en siffra för att undvika en if-sats, men inte alls i samma grad som den inbyggda kompilatorn. Ska man mikrooptimera så är det viktigt att testa koden för att hitta det som fungerar snabbast på den datorn där koden ska köras.
4: Inline-funktioner drar ner prestandan lite (i det här fallet runt 5%)
4: Inline-funktioner drar ner prestandan lite (i det här fallet runt 5%)
Not: Kom alltid ihåg att det finns saker som gör att det inte blir som man tror att det är som: Kompilatorn optimerar på ett sätt som man inte vet om, cache-missar och branch-prediction (varje grej förtjänar några googlingar på internettet.)