Berechnung mit OpenMP zu langsam
-
Danke für deine Antwort.
Ich habe den LUT aufgeteilt sodass in jeder 3er for-Schleife max 20MB sind.
Außerdem habe ich einige if's vor die Iteration gezogen.
Der Geschwindigkeitsboost von 12,8s -> 6,3s kommt aber von den vorgezogenen if's nicht von der Aufteilung.Habe jetzt folgende Werte:
1 Thread: 6,3s
2 Threads: 3,3s
4 Threads: 2,1s
8 Threads: 1,4s
16 Threads: 1,3s
32 Threads: 1,3sKeine Verbesserung ab 8 Threads
Ich verwende den Intel Compiler mit folgen flags -O3 -openmp -xCORE-AVX-I
Die flags -no-prec-div -ansi-alias haben nichts gebracht.
Gibt es denn noch andere flags die ich benutzen soll. Ich habe nach einer Single precision flag gesucht aber nichts gefunden.//Global Variablen float matrix1[175]; float matrix2[175]; float matrix3[175]; float matrix4[175]; void RunApproximation(float lut3Linear[], float iterations1, float iterations2, float iterations3) { //RunApproximation führt eine approximierte Berechnung aus und liefert gute Startwerte für die RunFull-Funktion. Die Startwerte werden in den Matrizen 1-4 gespeichert. } void RunFull(float lut3Linear[], float iterations1, float iterations2, float iterations3) { //Anzahl der Threads für openMP wird festgelegt omp_set_num_threads(32); //Die Iteration startet, welche nahezu die gesamte Berechnungszeit beansprucht for (int iter=1; iter<500; iter++) { //Matrix zwischenspeichern damit deterministisch float matrix1Det[175]; for (int I=0; I<175; I++) { matrix1Det[I] = matrix1[I]; } //Ein Gewicht wird berechnet, für die spätere aktuallisierung der gloablen Matrizen 1-4 float gewicht = 1.0f / (float)Iter; //Es werden ein paar einfache schnelle Berechnungen ausgeführt. float matrix1Prozent = 0.0f; for (int I=0; I<175; I++) { matrix1Prozent = matrix1Prozent+2.0f*matrix1Det[I]; } matrix1Prozent = matrix1Prozent*0.145f; ... //Hier startet meine Parallelisierung #pragma omp parallel for for (int a=0; a<44; a++) { //Es werden bei jedem Schleifendurchlauf bestimmte Berechnungswerte auf 0 gesetzt float berechnung1 = 0.0f; float berechnung2 = 0.0f; float berechnung3 = 0.0f; ... for(int b=0; b<175; b++) { for(int c=0; c<175; c++) { //Es werden ein paar Zwischenergebnisse berechnet die häufiger benötigt werden float zwischenErgebnis1 = a*85683+b*507+c*3; float zwischenErgebnis2 = matrix1Det[b] * matrix2Det[c]; float zwischenErgebnis3 = matrix3Det[b] * matrix4Det[c]; ... //Mehrer Berechnungen finden statt und werden jeweils aufaddiert berechnung1 = berechnung1 + ((3.0f*iterations1) * lut3Linear[zwischenErgebnis1] - iterations2) * zwischenErgebnis2; berechnung2 = berechnung2 + ((2.0f*iterations1) * lut3Linear[zwischenErgebnis1+1] - iterations2) * zwischenErgebnis3; berechnung3 = berechnung3 + ((3.0f*iterations2) * lut3Linear[zwischenErgebnis1] - iterations3) * zwischenErgebnis2; ... } } //Wieder einige Berechnungen berechnung1 = berechnung1 / berechnung2; //Hier findet ein Vergleich statt und anschließend werden die gloalen Matrizen 1-4 aktuallisiert if (berechnung1 > (-1.0f)) { matrix1[a]=((1.0f-gewicht)*matrix1[a]) + gewicht; } else { matrix1[a]=((1.0f-gewicht)*matrix1[a]); } ... } //Hier endet die Parallelisierung #pragma omp barrier #pragma omp parallel for for (int a=0; a<44; a++) { //Hier steht das gleiche, wie in der ersten for-Schleife nur mit LUT für diese a's #pragma omp barrier #pragma omp parallel for for (int a=0; a<44; a++) { //Hier steht das gleiche, wie in der ersten for-Schleife nur mit LUT für diese a's #pragma omp barrier #pragma omp parallel for for (int a=0; a<43; a++) { //Hier steht das gleiche, wie in der ersten for-Schleife nur mit LUT für diese a's #pragma omp barrier } //Hier werden dann noch die berechneten Matrizen ausgegeben .... } int main(int argc, char *argv[]) { //Daten werden aus Datei einlesen float *lut3Linear = (float*)malloc(sizeof(float)*175*175*175*9); FILE * lut3LinearFile; lut3LinearFile = fopen("LUT3linearFloat.dat", "rb"); for(int a=0; a<175; a++) { for(int b=0; b<175; b++) { for(int c=0; c<175; c++) { for(int d=0; d<9; d++) { fread(&lut3Linear[a*175*175*9+b*175*9+c*9+d], sizeof(float), 1, lut3LinearFile); } } } } fclose (lut3LinearFile); ... //Der Benutzer soll solange Berechnungen durchführen können, bis die Anwendung gekillt wird while(1) { //Daten werden per Console vom Benutzer abgefragt float eingabe1, eingabe2; cin >> eingabe1; cin >> eingabe2; ... //Aus der Eingaben vom Benutzer werden neue Werte für die Iteration berechnet float iterations1, iteration2, iteration3; ... //Iteration wird aufgerufen RunApproximation(lut3Linear, iterations1, iteration2, iteration3); RunFull(lut3Linear, iterations1, iteration2, iteration3); } }
-
hustbaer schrieb:
Also...
Ein paar mehr Fragen. Ich vermute nämlich dass du doch nicht durch die Speicherbandbreite limitiert bist.- Welche Zeit misst du, nur die die für
RunFull()
benötigt wird, oder was das gesamte Programm braucht? - Wird die Berechnung
zwischenErgebnis1 = a*85683+b*507+c*3;
+ darauffolgenden Zugriff auf den LUT mitzwischenErgebnis1
als Index genau so gemacht, oder das du das "verbeispielt"? - Warum
float zwischenErgebnis1
- der Wert ist immer ganzzahlig, und wenn du die Variable als Index in ein Array verwendest solltest du auch nenint
(oderunsigned int, size_t
) verwenden. - Wie viele echte Cores und wie viele "Hardware Threads" hat die Maschine auf der das läuft. Auf nem Quad-Core mit 32 Threads wird das z.B. nicht deutlich schneller werden. Der von dir genannte E5-2680 hat ja 10 Kerne -- ist dann halt die Frage wie viele solche E5-2680 in dem Teil drinstecken das deinen Code ausführt.
Abhängig von den Antworten auf diese Fragen dann
a) Miss mal getrennt nur die Zeit dieRunFull()
benötigt und was das gesamte Programm braucht. Kann ja sein dass derRunFull()
Teil bereits perfekt skaliert, nur der Rest halt nicht.
b) Ersetze mal das "jeden Float einzeln mitfread
einlesen" durch einen einzigenfread
Aufruf.
c) Was du probieren kannst, aber vermutlich nicht viel bringen wird (oder u.U. sogar schaden kann): setz mal das#pragma omp parallel for
eins weiter nach innen, also vor die "b" Schleife. Falls du wirklich (was ich wie gesagt nicht mehr glaube) durch die Speicherbandbreite limitiert bist, sollte das was bringen. Siehste dann ja.
d) Geh nochmal den gesamten Code durch und guck ob du noch weitere "float
woint
sein sollte" Fälle o.Ä. findest.zu 1) habe die Gesamtzeit gemessen. Habe aber auch schon mal getrennt gemessen und RunFull() hat nur gewicht
zu 2) das wird wirklich so gemacht
zu 3) das stimmt, ist immer ganzzahlig
zu 4) stimmt der hat 10, hab htops während der Berechnung laufen lassen und das zeigt mir 32 Kerne an. Allerdings haben diese eine max. Auslastung von ~70%. Könnte auch an der Trägheit liegen, da die Berechnung ja nur kurz ist.zu a) siehe 1)
zu b) die LUT werden ja nur einmal eingelesen, diese Zeit wurde auch nicht gemessen und ist eigt. auch egal
zu c) muss ich dann noch testen
zu d) sonst nichts weiter gefunden. ist es eigt. besser float * float zu machen anstatt float * int, weil theoretisch könnte ich meine LUTs auch auf int umstellen, da es bei der ganzen Sache nur um Verhältnisse geht
- Welche Zeit misst du, nur die die für
-
Ist openmp wirklich akzeptabel für sowas? So allgemein unter Entwicklern meine ich.
Ich würde das nie machen, allein schon weil ich nicht weiß was genau das Ding im Hintergrund macht. Ich würde auf jeden Fall von Hand threads starten und Funktionspointer übergeben.
-
randa schrieb:
Ist openmp wirklich akzeptabel für sowas? So allgemein unter Entwicklern meine ich.
Ich würde das nie machen, allein schon weil ich nicht weiß was genau das Ding im Hintergrund macht. Ich würde auf jeden Fall von Hand threads starten und Funktionspointer übergeben.
Und wie würdest du eine Schleife parallisieren?
#pragma omp parallel for for(int i = 0; i < n; i++) { arr[i] = func(); }
Das mit (std::)Threads nachzubauen sollte gehen, ist aber ziemlich viel Arbeit, dafür dass du dich gegen einen Standard durchsetzen willst. (Und du kannst nicht durch ein Compilerflag sämtliche Parallelisierung rausnehmen...)
Und die Jungs, die OpenMP implementieren auf den einzelnen Compilern, haben gewöhnlich schon ein bisschen Ahnung und wissen, was sie machen.
Es ist halt für High Performance Computing und nicht um mal eben in ner Business Application einen Thread Pool für Requests zu managen.
Ich hab mir jetzt zwar nicht den den Code vom OP im Detail angeschaut, aber das sieht schon ganz gut aus, was die Zuständigkeit von OpenMP angeht.
-
Skym0sh0 schrieb:
Und wie würdest du eine Schleife parallisieren?
#pragma omp parallel for for(int i = 0; i < n; i++) { arr[i] = func(); }
naja ich würde jeden Thread n/n_threads elemente durchlaufen lassen
Es ist halt für High Performance Computing und nicht um mal eben in ner Business Application einen Thread Pool für Requests zu managen.
gerade da würde ich darauf bestehen dass ich alles von Hand mache. Deswegen frage ich ob das in computing projekten auch wirklich einfach mit einer handvoll #pragmas abgekanzelt wird, ich zweifle.
-
randa schrieb:
Skym0sh0 schrieb:
Und wie würdest du eine Schleife parallisieren?
#pragma omp parallel for for(int i = 0; i < n; i++) { arr[i] = func(); }
naja ich würde jeden Thread n/n_threads elemente durchlaufen lassen
Und wie siehts aus mit?
#pragma omp parallel for schedule(guided) for(int i = 0; i < n; i++) { arr[i] = func(); }
randa schrieb:
Es ist halt für High Performance Computing und nicht um mal eben in ner Business Application einen Thread Pool für Requests zu managen.
gerade da würde ich darauf bestehen dass ich alles von Hand mache. Deswegen frage ich ob das in computing projekten auch wirklich einfach mit einer handvoll #pragmas abgekanzelt wird, ich zweifle.
Naja, es gibt ja noch andere Tricks/Frameworks, z.B. MPI.
OpenMP bietet halt noch sehr viel Stuff mehr als nur eine kleine Kapsel um Threads.Umgekehrt gebe ich dir Recht, dass gerade mit der std::Threading Bibliothek viele coole Sachen hinzugekommen sind wie der Mutex-Lock im RAII Objekt gekapselt
-
randa schrieb:
Deswegen frage ich ob das in computing projekten auch wirklich einfach mit einer handvoll #pragmas abgekanzelt wird, ich zweifle.
Wieso nicht?
Der OpenMP/Cilk/AMP/TBB/... Scheduler ist weitaus performanter als alles was man selbst mit vertretbarem Aufwand hinbekommt.
-
@Simon S.
Hast du mal probiertnum_threads
beim#pragma parallel for
zu verwenden statt bzw. zusätzlich zuomp_set_num_threads
?
Oder mal nen anderen Compiler probiert (MSVC, GCC)?
-
hustbaer schrieb:
Wieso nicht?
man kann nicht sehen was konkret am Ende für ein Code produziert wird. Und bei parallelisierung und kritischen Anwendungen will ich das genau wissen.
-
Klassische Noob-Paranoia.
-
hustbaer schrieb:
Klassische Noob-Paranoia.
ich?
-
Klar, wer sonst.
Google auch gerne mal NIH (not invented here).Bzw. allgemein Dinge selbst zu implementieren nur weil man meint (ohne gute Grundlage) dass die fertigen Implementierungen evtl. nicht gut genug sein könnten...
Trotz dem diese von Teams entwickelt wurden die sich wirklich mit der Materie auskennen...
Das macht keinen Sinn.Passt aber genau zu dir.
ps: Wenn man früh Testläuft macht, kommt man üblicherweise recht schnell drauf ob das gewählte Tool den Anforderungen entspricht. Und dann tauscht man es einfach aus, bzw. schreibt dann was eigenes. Bzw. wenn die Softwarearchitektur halbwegs sauber ist, ist es auch kein grosses Problem wenn man erst sehr spät draufkommt.
-
hustbaer schrieb:
@Simon S.
Hast du mal probiertnum_threads
beim#pragma parallel for
zu verwenden statt bzw. zusätzlich zuomp_set_num_threads
?
Oder mal nen anderen Compiler probiert (MSVC, GCC)?Hat nichts geändert. Der Intel war am besten, habe die anderen beiden schon getestet.
Habe jetzt noch paar Messungen mit verschiedenen Varianten durchgeführt.
Variante0:(ohne RunFull(), läuft in einem Thread)
0,2s
Diese müssen also bei allen folgenden Werten abgezogen werden um die Zahlen für RunFull() zu erhalten.Variante1:(ein großer LUT)
1 Thread: 13s
2 Threads: 7s
8 Threads: 4s
16 Threads: 2s
32 Threads: 1,5sVariante2:(ein großer LUT und alle Schleifen guided)
32 Threads: 1,22sVariante3:(Schleifen 42, 42, 42, 43)
1 Thread: 6,3s
2 Threads: 3,3s
4 Threads: 2,1s
8 Threads: 1,4s
16 Threads: 1,3s
32 Threads: 1,3sVariante4: (Schleifen 42, 42, 42, 43 und alle Schleifen guided)
8 Threads: 1,22s
16 Threads: 0,92s
32 Threads: 1,15sVariante5: (Schleifen 32, 32, 32, 32, 41)
8 Threads: 1,46s
16 Threads: 1,24s
32 Threads: 1,33sVariante4: (Schleifen 32, 32, 32, 32, 41 und alle Schleifen guided)
8 Threads: 1,23s
16 Threads: 1,07s
32 Threads: 1,06sVariante6: (Schleifen 32, 32, 32, 32, 41 nur Schleife mit 41 guided)
8 Threads: 1,55s
16 Threads: 1,14s
32 Threads: 1,18sDie CPU-Auslastung ist wie gesagt laut htops nicht bei 100%.
z.B. bei Variante 4 32Threads nur bei 50%
Da später mal noch mehrere Berechnungen in die 3er for-Schleife kommen, würde sich die Berechnungszeit erstmal hoffentlich nicht erhöhen und die CPU-Auslastung Richtung 100% gehen, oder ist das Quatsch?So richtig zufrieden bin ich mit den Ergebnissen nicht wirklich. Ich würde mich jetzt für die Variante 4 entscheiden.
Es ist wohl guided all > guided last > non guided und LUT split passend zu threadzahl > LUT split random > LUT bigZu arr[i] = func();
Das verstehe ich nicht. Wieso soll ich die Werte erst in ein Array speichern.
Ist das so gedacht, dasarr[i] = ((3.0f*iterations1) * lut3Linear[zwischenErgebnis1] - iterations2) * zwischenErgebnis2;
sich leichter vektorisieren lässt und nach der Vektorisierung wird das Array aufaddiert um auf
berechnung1 = berechnung1 + ((3.0f*iterations1) * lut3Linear[zwischenErgebnis1] - iterations2) * zwischenErgebnis2;
zukommen.
Ich bin für jeden weiter Hilfe sehr dankbar und möchte mich bei allen für ihre Antworten bedanken.