Ik ben wat aan het klooien met OpenCL. Hiervoor heb ik een N-body simulator gemaakt, voor het testen gebruik ik N=4096. Dit algoritme heb ik zowel in OpenCL als in C++ (voor de CPU, met AVX instructies) geschreven.
CPU:
Op stackoverflow, hier en hier wordt aangegeven hoe je het maximum aantal Gflops bereikt. Op mijn sandy bridge CPU haal je dat door afwisselend een multiply en add uit te voeren. Mijn code heeft niet evenveel multiply's als add's, dus het maximum is sowieso niet te halen. Desondanks haalt mijn implementatie iets meer dan 50% van het theoretische maximum aan Gflops.
GPU:
Mijn GPU is een NVS 4200m.

Deze bestaat uit 1 SM van de fermi architectuur. Draaiend op 2x720Mhz. Het theoretisch aantal Gflops is aantal cores * kloksnelheid * 2 = 48 * 1440 * 2 = 138.24 Gflops. Diverse bronnen melden een maximum van 155Gflops. Maar het gaat daar om GPU's die op 800Mhz draaien.
Mijn kernel zit er als volgt uit:
Let op: regel 13 hoort in feite mass / sqrt( len2 )3 te zijn. Dat doen we nu even niet, dat vergemakkelijkt het rekenen.
In dit loopje worden 11 floating point berekeningen uitgevoerd. Dat gebeurt 40962 keer per frame, 2864 us per frame (volgens de profiler). We halen hierbij 64 Gflops. Dat is iets minder dan de helft van het theoretisch haalbare Gflops.
De nvidia visual profiler geeft aan dat ik compute bound ben, elke kernel 22 registers nodig heeft, en er 1024 threads naast elkaar gedraaid worden. De code zo aanpassen dat er niet over de volledige 4096 elementen geloopt wordt levert geen snelheidswinst op.
Ik heb 2 vragen:
Waarom draait deze kernel niet sneller.
Met welke magische kernel kan ik dan wél het theoretische aantal Gflops halen? De eerste stackoverflow link demonstreert hoe je dat met een CPU voor elkaar krijgt. Maar hoe doe ik dat met een GPU? Met 48k memory totaal lijkt mij cache niet het probleem (de cache L1 hit rate is ook achterlijk hoog, 99.85%)
De assembly ziet er trouwens als volgt uit, maal 16
Ik neem aan dat je memory load's en multiply in één clock cycle kan doen.
CPU:
Op stackoverflow, hier en hier wordt aangegeven hoe je het maximum aantal Gflops bereikt. Op mijn sandy bridge CPU haal je dat door afwisselend een multiply en add uit te voeren. Mijn code heeft niet evenveel multiply's als add's, dus het maximum is sowieso niet te halen. Desondanks haalt mijn implementatie iets meer dan 50% van het theoretische maximum aan Gflops.
GPU:
Mijn GPU is een NVS 4200m.

Deze bestaat uit 1 SM van de fermi architectuur. Draaiend op 2x720Mhz. Het theoretisch aantal Gflops is aantal cores * kloksnelheid * 2 = 48 * 1440 * 2 = 138.24 Gflops. Diverse bronnen melden een maximum van 155Gflops. Maar het gaat daar om GPU's die op 800Mhz draaien.
Mijn kernel zit er als volgt uit:
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| float* xIn = hele lang array van X coordinaten; float* yIn = hele lang array van Y coordinaten; float* massIn = hele lang array van massa van de elementen float newdx = resulterende kracht; float newdy = resulterende kracht; for( int i = 0; i < 4096; i++ ) { float xDiff = out.x - xIn[i]; float yDiff = out.y - yIn[i]; float x2 = xDiff * xDiff + 0.0001f; // small bias. Gratis vanwege mad (multiply-add) float len2 = yDiff * yDiff + x2; // mad float factor = massIn[i] * len2; newdx = xDiff * factor + newdx; // mad newdy = yDiff * factor + newdy; // mad // 16x unrolled } |
Let op: regel 13 hoort in feite mass / sqrt( len2 )3 te zijn. Dat doen we nu even niet, dat vergemakkelijkt het rekenen.
In dit loopje worden 11 floating point berekeningen uitgevoerd. Dat gebeurt 40962 keer per frame, 2864 us per frame (volgens de profiler). We halen hierbij 64 Gflops. Dat is iets minder dan de helft van het theoretisch haalbare Gflops.
De nvidia visual profiler geeft aan dat ik compute bound ben, elke kernel 22 registers nodig heeft, en er 1024 threads naast elkaar gedraaid worden. De code zo aanpassen dat er niet over de volledige 4096 elementen geloopt wordt levert geen snelheidswinst op.
Ik heb 2 vragen:
Waarom draait deze kernel niet sneller.
Met welke magische kernel kan ik dan wél het theoretische aantal Gflops halen? De eerste stackoverflow link demonstreert hoe je dat met een CPU voor elkaar krijgt. Maar hoe doe ik dat met een GPU? Met 48k memory totaal lijkt mij cache niet het probleem (de cache L1 hit rate is ook achterlijk hoog, 99.85%)
De assembly ziet er trouwens als volgt uit, maal 16
ld.global.f32 %f29, [%r18+4]; // x sub.f32 %f30, %f2, %f29; ld.global.f32 %f31, [%r19+4]; // y sub.f32 %f32, %f3, %f31; fma.rn.f32 %f33, %f30, %f30, 0f38D1B717; // 0.0001 fma.rn.f32 %f34, %f32, %f32, %f33; ld.global.f32 %f35, [%r20+4]; // mass mul.f32 %f36, %f35, %f34; fma.rn.f32 %f37, %f30, %f36, %f27; // factor * x + dx fma.rn.f32 %f38, %f32, %f36, %f28; // factor * y + dy
Ik neem aan dat je memory load's en multiply in één clock cycle kan doen.