Fragen? Antworten! Siehe auch: Alternativlos
Um mal das konkrete Problem zu beziffern, das ich gerade zu lösen versuche: Ich suche in meinem Index über ca 23 GB Quellcode, ca 550k Dateien. Mein Index grenzt die Suche nach memcpy auf ungefähr 23000 Dateien ein. Diese Dateien muss ich dann alle einmal öffnen und die Treffer mit Zeilennummer ausgeben. Das ist die Aufgabe dieser Suchmaschine.
Der alte Code braucht dafür ca 2 Sekunden (wenn alle Dateien im Cache sind, sonst natürlich deutlich länger). Der neue Code braucht hier gerade nur 1,17 Sekunden.
Eine spannende Lektion habe ich aber noch. Ich habe den Code einmal auf Futures umgestellt, das ist ein Feature von C++11. So sieht der Ausgabe-Code ohne Futures aus:
for (auto& x : hits)Und so sieht er mit Futures aus:
cout << grep(words,x.first);
vector<future<string>> v(hits.size());Die Idee ist, dass zwischen dem async und dem x.get() die Arbeit aus den Futures auf einen Threadpool verteilt wird, und wenn ich unten x.get auf ein Element in dem Vector aufrufe, dann macht der Code einen Thread-Join, falls der Thread nicht eh schon von sich aus fertig war. Die ganze Synchronisation findet also implizit statt.
size_t i=0;
for (auto& x : hits)
v[i++]=async(grep,words,x.first);
for (auto& x : v)
cout << x.get();
Die erste Ernüchterung: Nix Threadpool. Die Standardpolicy von g++ 4.9.2 unter Linux ist, das dann statt async als deferred auszuführen, d.h. da werden keine Threads gestartet sondern bei x.get() wird das halt schnell synchron ausgeführt. Das ist dann natürlich nicht schneller als es direkt synchron auszuführen. m(
Aber man kann auch g++ 4.9.2 unter Linux sagen, dass man gerne Threads haben will:
for (auto& x : hits)Damit hab ich dann statt 100% CPU immerhin ~300% CPU (also drei Cores werden ausgelastet), aaaaaaber: es ist nicht schneller als es synchron zu machen. Im Gegenteil. Das kostet plötzlich rund 1,6 Sekunden. Das Konzept mit den Futures heißt in anderen Sprachen auch Promise. Es gibt auch in C++ Promise als Konzept. Ich habe den Unterschied zwischen Future und Promise noch nicht verstanden, und es gibt auch noch ein drittes Ding, Packages, das auch sowas ist. (Ihr müsst mir das jetzt nicht erklären, ich kann das Manual schon auch lesen, habe es lediglich noch nicht getan an der Stelle)
v[i++]=async(std::launch::async,grep,words,x.first);
Was ich jedenfalls sagen will: Abstraktionen sind ja schön und gut, aber wenn ich dann am Ende Hand anlegen muss, damit das einen Speedup ergibt, dann ist das für den Fuß. Vielleicht übersehe ich irgendwas gerade, aber aus meiner Sicht ist das hier gerade die perfekt parallelisierbare Sache, eine Datei öffnen, darin Matches suchen, und die Ergebnisse dann seriell ausgeben. Vielleicht bringt das ja nur was, wenn er dafür tatsächlich zum Massenspeicher gehen muss, und wenn das eh aus dem Buffer Cache des Systems kommt, dann hat man keinen Vorteil aber trägt die Kosten für die implizite Synchronisierung. Oder so. Ich weiß es nicht. Ich untersuche das jetzt auch nicht mehr :-)
(Es geht hier übrigens gerade nicht darum, wie man schönen C++-Code schreibt, sondern wie Futures aussehen)
Update: Äh, gcc 4.9.2 nicht 2.9.2 :)
Update: Einige Leute schlagen jetzt OpenMP vor. Ja, mit OpenMP geht das, sogar richtig gut. Damit komme ich dann auf 0,58 Sekunden. Mein Punkt mit diesem Blogeintrag war, dass dieser Standard so nicht hätte ausgerollt werden dürfen. Jetzt wird eine Generation C++-Programmierer lernen, dass man sein Threadpooling doch von Hand oder mit 3rd-Party-Kram wie OpenMP macht, und das erschafft einen Haufen an Legacy-Code, den wir in Zukunft werden warten müssen.