2016-06-22 6 views
2

Ich habe folgende (Pseudo-) Code Schnipsel in meinem Kernel:reduzieren Indirekt in OCL Code

kernel void krnl(global X* restrict x){ 
    for(int i = 0; i < 100; i++){ 
     x[a].y[b].z[i] * x[a].y[i].n; 
    } 
} 

Ich bin ein Xilinx FPGA-Gerät, so dass einige Dinge vielleicht ein bisschen anders sein. Der Code funktioniert auf GPU/CPU, aber nicht auf dem FPGA. Nur ein paar Details, wenn es jemandem hilft.

x, y, z und n sind die folgenden:

typedef struct Y 
{ 
    float n; 
    float z[MAXSIZE] 
} Y; 

typedef struct X 
{ 
    int i; 
    Y y[MAXSIZE]; 
} X; 

'a' und 'b' nur Vektoren Int.

Ich muss die Anzahl der Umleitungen reduzieren, idealerweise nur die Kernel ändern (oder Inline-Funktionen hinzufügen - ich habe es versucht, aber es scheint, als würden die Daten nicht an die Kernel weitergegeben).

konkret, ich brauche den Code, so etwas zu sein:

for(int i = 0; i < 100; i++){ 
    V.z[i] * K[i].n; 
} 
+0

Ich habe eine Frage dazu in einem anderen Thread geschrieben, weil ich fühlte, wie die Natur der Frage war ganz anders, werfen Sie einen Blick, wenn Sie möchten: http://stackoverflow.com/questions/37981455/using -async-work-group-copy-with-a-custom-data-type Ich glaube, die Lösung für dieses Problem wird alle Daten von __global zu __local übergeben, verwenden Sie die __local Daten in den Schleifen, dann übergeben Sie es zurück __global. Ich habe diesen Code jedoch noch nicht fertiggestellt und werde noch keine voreiligen Schlüsse ziehen. Wenn es funktioniert, werde ich die Lösung hier veröffentlichen. – jiuwo

Antwort

2

Nun, wenn a und b während der Schleife statisch sind dann tun:

kernel void krnl(global X* restrict x){ 
    const float* fast_l = x[a].y[b].z; 
    const Y*  fast_r = x[a].y; 
    for(int i = 0; i < 100; i++){ 
     fast_l[i] * fast_r[i].n; 
    } 
} 

Es ist für die ganz typisch ist Compiler, um globale Lese- und Schreibvorgänge nicht zwischenzuspeichern, da globale Daten als sehr flüchtig angesehen werden (Zugriff durch alle Arbeitselemente). Das manuelle Zwischenspeichern hilft normalerweise bei der Lösung dieser Probleme.

Allerdings sollte ein cleverer Compiler in der Lage sein zu erraten, dass all diese Indirektion nur ein einfacher Zeigeroffset ist. Ich würde nicht denken, dass Sie in diesem Fall einen Gewinn erzielen werden. Beispiel:

kernel void krnl(global X* restrict x){ 
    const float* off_l = ((float *)x)+sizeof(X)*a+sizeof(Y)*b+sizeof(int); 
    const float* off_r = ((float *)x)+sizeof(X)*a; 
    for(int i = 0; i < 100; i++){ 
     *(off_l+i) * *(off_r+sizeof(Y)*i); 
    } 
} 
+0

Danke für die Antwort. Leider hat der Code das gleiche Ergebnis wie mein vorheriger Versuch und macht den Code nicht effizienter. – jiuwo

+0

Entschuldigung, ich möchte mit meinem vorherigen Kommentar etwas genauer sein. Ich glaube der Grund, warum die Leistung nicht ändert, ist, dass der Kernel noch die gleiche Anzahl von Aufrufen auch nach den Änderungen macht. Ich versuche derzeit, die Daten in __local vor dem Aufruf zu verschieben, aber ich habe einige Probleme wegen der "num_elements" in async_work_group_copy. Ich werde dieses oder das OP aktualisieren, wenn ich es nicht funktioniere, und wenn es funktioniert, werde ich mit der Lösung zurückkommen. Danke noch einmal. – jiuwo

1

Sie brauchen weniger Umleitung, um weniger Speicheroperationen zu haben?

Diese für eine gpu besser sollte:

typedef struct X 
{ 
    Y y[MAXSIZE]; 
    int i; 
} X; 

statt

typedef struct X 
{ 
    int i; 
    Y y[MAXSIZE]; 
} X; 

, weil es weniger Speicheroperationen pro Artikel lesen müssen konnte, weil zuerst in ursprünglichen Struktur Leseoperation während letztere weniger Effizienz hat struct kann es bei voller Effizienz durchführen.

Wenn das funktioniert, dies:

typedef struct Y 
{ 
    float z[MAXSIZE]; 
    float n; 
} Y; 

sollte schneller sein zu besonders wenn MAXSIZE eine gerade Zahl ist.

Trennung von Y von i als zwei verschiedene Arrays anstelle von Array von Objekt von Y + i, wird für GPU schneller sein. Das gleiche gilt für z und n in Y. Reine Arrays von nativen Elementen sind schneller, insbesondere nur dann, wenn eines der Felder benötigt wird und andere nicht benötigt werden.

Das Hinzufügen von Dummy-Floats/Ints am Ende jeder Struktur könnte ebenfalls die Leistung ändern.

Die beste Leistung erfordert Thread-Level-Parallelität und zusammenhängende Lesevorgänge aus dem Speicher, während der objektorientierte Ansatz Lesbarkeit, Nachhaltigkeit und Aufrüstbarkeit bietet, aber keine Portabilität, da einige Hardware Probleme mit der Ausrichtung haben. Warum laden Sie den ganzen Float z [MAXSIZE] aus dem Speicher, während Sie nur z [i] benötigen? Weil es in einem Objekt ist.Wenn es ein reines Array wäre, würde es nur 1 Indexierungsoperation benötigen, um z [i] zu erhalten. Das Laden eines Objektfeldes erfordert einen Sprung des MAXSIZE-Schritts im Speicher, selbst wenn es nur einen einzelnen Gleitkommawert lädt, aber eine reine Array-Version Ihrer Wahl würde es mit einer Größe von 1 schreiten lassen und vielleicht optimale Geschwindigkeit erreichen.

Beispiel Array für z:

z[0]: 1st thread's z[0] 
z[1]: 2nd thread's z[0] 
z[2]: 3rd thread's z[0] 
.... 
z[n]: 1st thread's z[1] 
z[n+1]: 2nd thread's z[1] 
z[n+3]: 3rd thread's z[1] 
.... 

so bei jedem Schritt des

for(int i = 0; i < 100; i++) 

GPU Zugriffe auf den Speicher in einem nicht-durchlöcherten Weise für alle z, die viel schneller als objektorientierten Version wäre meiner bescheidenen Meinung nach.

+0

_Es könnte weniger Speicherleseoperationen pro Element erfordern, da die erste Leseoperation in der ursprünglichen Struktur eine geringere Effizienz hat, während die letzte Struktur dies bei voller Effizienzoperation tun kann. Dies ist interessant zu wissen. Ich benutze tatsächlich ein Xilinx FPGA, also glaube ich nicht, dass einige der Dinge, die du gesagt hast, auf meine Arbeit zutreffen. Ich dachte nicht, dass es angemessen wäre, dies zu spezifizieren, da es nicht ganz auf opencl oder C++ - Code bezogen ist, aber ich werde es das nächste Mal spezifizieren. Danke für deine Antwort. – jiuwo

+0

Dann arrangiert der Compiler Lese- und Schreibvorgänge auf Hardwareebene, so dass es keine Softwareoptimierung benötigt? –

+0

In meinem Fall, wenn der Kernel nicht genug optimiert ist, scheint es, dass es nicht auf dem FPGA-Fabric laufen wird. Ich glaube, dass die (möglicherweise ineffiziente oder unpassende) Verwendung von Objekten im Kernel einer der Faktoren sein könnte, die dies verursachen, daher die Frage. – jiuwo

Verwandte Themen