2017-01-22 1 views
2

Ich beginne gerade mit der Verwendung von simd intrinsics. Mein Profiler hat gezeigt, dass viel Zeit für die Vertexinterpolation aufgewendet wird. Ich ziele auf AVX2 und versuche eine Optimierung für das Folgende zu finden - da ich 3 Vektor2s habe, die eine Interpolation benötigen, stelle ich mir vor, ich sollte sie in ein einziges __m256 laden können und die Multiplikation durchführen und effizient addieren. Hier ist der Code, den ich zu konvertieren versuche - lohnt es sich, es als 256-Bit-Operation zu tun? Die Vektoren sind nicht ausgerichtet.AVX2 baryzentrische Interpolation einer Vertexkomponente

Vector2 Interpolate(Vector3 uvw, Vector2 v0, Vector2 v1, Vector2 v2) 
{ 
    Vector2 out; 
    out = v0 * uvw.x; 
    out += v1 * uvw.y; 
    out += v2 * uvw.z; 

    return out; 
} 

struct Vector2 { float x; float y; } ; 
struct Vector3 { float x; float y; float z; } ; 

Meine Frage ist - wie kann ich drei unaligned vector2 in den einzelnen 256-Bit-Register geladen werden, so kann ich das mehrfach tun und fügen?

Ich verwende VS2013.

+2

Zu viel lästige Datenbewegung wäre notwendig. Wenn Sie die Scheitelpunkte (all jene, die interpolieren müssen, nicht nur 3) und die Skalierungsfaktoren als Arrays übergeben, könnten Sie tatsächlich vernünftigen Code schreiben – harold

+0

@harold Wie viele Elemente zu einer Zeit würde es sich lohnen? 16 Sätze? 256 Sätze? – Steven

+2

Wie wäre es mit 'struct Vector2_block {float8 x; float8 y; }; 'und' struct Vector3_block {float8 x; float8 y; float8 z; }; 'und dann operierst du 8 Eckpunkte gleichzeitig. –

Antwort

1

Mir war langweilig, so ich es geschrieben habe, nicht getestet (aber kompiliert, die beide Clang und GCC aus dieser vernünftigen Code machen)

void interpolateAll(int n, float* scales, float* vin, float* vout) 
{ 
    // preconditions: 
    // (n & 7 == 0) (not really, but vout must be padded) 
    // scales & 31 == 0 
    // vin & 31 == 0 
    // vout & 31 == 0 

    // vin format: 
    // float v0x[8] 
    // float v0y[8] 
    // float v1x[8] 
    // float v1y[8] 
    // float v2x[8] 
    // float v2y[8] 
    // scales format: 
    // float scale0[8] 
    // float scale1[8] 
    // float scale2[8] 
    // vout format: 
    // float vx[8] 
    // float vy[8] 

    for (int i = 0; i < n; i += 8) { 
    __m256 scale_0 = _mm256_load_ps(scales + i * 3); 
    __m256 scale_1 = _mm256_load_ps(scales + i * 3 + 8); 
    __m256 scale_2 = _mm256_load_ps(scales + i * 3 + 16); 
    __m256 v0x = _mm256_load_ps(vin + i * 6); 
    __m256 v0y = _mm256_load_ps(vin + i * 6 + 8); 
    __m256 v1x = _mm256_load_ps(vin + i * 6 + 16); 
    __m256 v1y = _mm256_load_ps(vin + i * 6 + 24); 
    __m256 v2x = _mm256_load_ps(vin + i * 6 + 32); 
    __m256 v2y = _mm256_load_ps(vin + i * 6 + 40); 
    __m256 x = _mm256_mul_ps(scale_0, v0x); 
    __m256 y = _mm256_mul_ps(scale_0, v0y); 
    x = _mm256_fmadd_ps(scale_1, v1x, x); 
    y = _mm256_fmadd_ps(scale_1, v1y, y); 
    x = _mm256_fmadd_ps(scale_2, v2x, x); 
    y = _mm256_fmadd_ps(scale_2, v2y, y); 
    _mm256_store_ps(vout + i * 2, x); 
    _mm256_store_ps(vout + i * 2 + 8, y); 
    } 
} 

Verwendet Format der Z-Boson, wenn ich ihn richtig verstanden. In jedem Fall ist es ein schönes Format, aus einer SIMD-Perspektive. Etwas unbequem aus einer C++ Perspektive.

Die FMAs serialisieren die Multiplikationen unnötigerweise, aber das sollte keine Rolle spielen, da sie nicht Teil einer Loop-getragenen Abhängigkeit ist.

Der vorhergesagte Durchsatz (unter der Annahme einer ausreichend kleinen Array) ist 2 Iterationen pro 9 Zyklen, Engpässe durch die Lasten. In der Praxis wahrscheinlich etwas schlechter, es gab einige Gespräche über einfache Läden stehlen gelegentlich p2 oder p3, so etwas, ich bin mir nicht wirklich sicher. Wie auch immer, das ist genug Zeit für 18 "FMAs", aber es gibt nur 12 (8 und 4 Mulps), so dass es nützlich sein kann, hier ein paar zusätzliche Berechnungen vorzunehmen, wenn es welche gibt.

+0

Danke - Ich werde es versuchen, wenn ich die Quelldaten in Streams umkonfigurieren kann. Im Moment ist der Raytracer wirklich auf einem "Per Ray" -Ergebnis eingestellt. – Steven

+0

Das ist im Allgemeinen, was ich getan habe - ich schrieb den Code um, um acht Strahlen auf einmal zu erzeugen und dann alles andere dazu zu bringen. Es funktioniert größtenteils, aber es gibt einige teure Operationen, bei denen ich eine Sammlung von Trefferobjekten mit abweichenden Daten nehme und sie mischen muss, aber das ist für eine andere Frage. Danke noch einmal. – Steven