2016-04-23 14 views
2

Ich versuche, um 90 Grad gegen den Uhrzeigersinn zu drehen und dann horizontal spiegeln.Arm Neon transponieren 4x4 uint32

Mein erster Ansatz war nur OpenCV zu verwenden:

cv::transpose(in, tmp); // transpose around top left 
cv::flip(tmp, out, -1); // flip on both axes 

Für Leistung, ich versuche, die beiden Funktionen in einem verschmelzen.

Mein Code:

void ccw90_hflip_640x480(const cv::Mat& img, cv::Mat& out) 
{ 
    assert(img.cols == 640 && img.rows == 480); 
    assert(out.cols == 480 && out.cols == 640); 

    uint32_t* imgData = (uint32_t*)img.data; 
    uint32_t* outData = (uint32_t*)out.data; 

    uint32_t *pRow = imgData; 
    uint32_t *pEnd = imgData + (640 * 480); 
    uint32_t *dstCol = outData + (480 * 640) - 1; 

    for(; pRow != pEnd; pRow += 640, dstCol -= 1) 
    { 
     for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol; 
      ptr != end; 
      ++ptr, dst -= 480) 
     { 
      *dst = *ptr; 
     } 
    } 
} 

dachte ich, die oben schneller sein würde, aber es ist nicht. Ich kann mir keinen Grund vorstellen, dass es nicht schneller wäre, neben OpenCV möglicherweise NEON zu verwenden.

fand ich diesen Artikel/Präsentation: http://shervinemami.info/NEON_RotateBGRX.swf

Die Umsetzung und Flipping sind so verschwommen zusammen, dass es sehr schwer macht, zu ändern, wo es in die andere Richtung drehen würde, und drehen um die horizontale Achse wie Ich brauche es auch. Der Artikel ist sehr alt, also hoffe ich, dass es einen einfacheren Weg gibt, das zu tun, was ich brauche.

Also, was ist der einfachste Weg, um eine 4x4-Matrix von UINT32 mit Arm NEON zu transponieren?

+0

_ "Für die Leistung" _? Wie haben Sie festgestellt, dass diese Bemühungen zu einem leistungsfähigeren Programm führen werden? –

+1

Tatsächlich bin ich in die Zukunft gereist, wo ich bestätigt habe, dass meine Armneonimplementierung schneller ist. Bevor ich den Algorithmus aufschreiben konnte, funktionierte meine Zeitmaschine leider nicht mehr und schickte mich in ein alternatives Universum, in dem jeder verknüpfte Listen anstelle von Vektoren verwendet. – bitwise

+1

Siehe [hier] (https://community.arm.com/thread/3002) für Lösungen (scrollen Sie etwas nach Code, der tatsächlich funktioniert). –

Antwort

1

Der folgende Code entspricht den OpenCV-Aufrufen im ursprünglichen Post, führt aber mehrmals (zumindest auf meinem Gerät) schneller.

Die Verwendung von Neon hat tatsächlich die Leistung signifikant erhöht. Da die Transposition innerhalb der CPU stattfindet, kann der Speicherzugriff rationalisiert werden, um Pixel in Vierergruppen zu lesen und zu schreiben, anstatt jeweils einzeln, wie in den Kommentaren erläutert.

void ccw90_hflip_640x480_neon(const cv::Mat& img, cv::Mat& out) 
{ 
    assert(img.cols == 640 && img.rows == 480); 
    assert(out.cols == 480 && out.cols == 640); 

    uint32_t *pRow = (uint32_t*)img.data; 
    uint32_t *pEnd = (uint32_t*)img.data + (640 * 480); 
    uint32_t *dstCol = (uint32_t*)out.data + (480 * 640) - (480 * 3) - 4; 

    for(; pRow != pEnd; pRow += 640 * 4, dstCol -= 4) 
    { 
     for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol; 
      ptr != end; 
      ptr += 4, dst -= 480 * 4) 
     { 
      uint32_t* in0 = ptr; 
      uint32_t* in1 = in0 + 640; 
      uint32_t* in2 = in1 + 640; 
      uint32_t* in3 = in2 + 640; 

      uint32_t* out0 = dst; 
      uint32_t* out1 = out0 + 480; 
      uint32_t* out2 = out1 + 480; 
      uint32_t* out3 = out2 + 480; 

      asm("vld1.32 {d0, d1}, [%[in0]] \n" 
       "vld1.32 {d2, d3}, [%[in1]] \n" 
       "vld1.32 {d4, d5}, [%[in2]] \n" 
       "vld1.32 {d6, d7}, [%[in3]] \n" 
       "vtrn.32 q0, q1    \n" 
       "vtrn.32 q2, q3    \n" 
       "vswp d1, d4     \n" 
       "vswp d3, d6     \n" 
       "vrev64.32 q0, q0    \n" 
       "vrev64.32 q1, q1    \n" 
       "vrev64.32 q2, q2    \n" 
       "vrev64.32 q3, q3    \n" 
       "vswp d0, d1     \n" 
       "vswp d2, d3     \n" 
       "vswp d4, d5     \n" 
       "vswp d6, d7     \n" 
       "vst1.32 {d6, d7}, [%[out0]] \n" 
       "vst1.32 {d4, d5}, [%[out1]] \n" 
       "vst1.32 {d2, d3}, [%[out2]] \n" 
       "vst1.32 {d0, d1}, [%[out3]] \n" 
       : 
       : [out0] "r" (out0), [out1] "r" (out1), [out2] "r" (out2), [out3] "r" (out3), 
        [in0] "r" (in0), [in1] "r" (in1), [in2] "r" (in2), [in3] "r" (in3) 
       : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7" 
       ); 
     } 
    } 
} 
1

Neon hilft nicht wesentlich Hinweis. Ihr Code bewegt Daten einfach; Neon kann den zugrunde liegenden Speicher nicht wesentlich schneller machen. Siehe this article; mit der PLD wird auch helfen. Ich würde vorschlagen, dass Sie dst in Reihenfolge verarbeiten und mit ptr herumspringen. Der Cache füllt ptr und dst füllt Zeile.

Dies ist eine alternative Form des Speichers durchqueren (variable Namen können nicht sinnvoll),

uint32_t *pEnd = imgData + 640; 
uint32_t *dstCol = outData; 

for(; pRow != pEnd; pRow ++) 
{ 
    for(uint32_t *ptr = pRow, *dst = dstCol, *end = dst + 480; 
     dst != end; 
     ptr += 640, dst++) 
    { 
     *dst = *ptr; 
    } 
    // could flush `dstCol` here as it is complete or hope the system clues in. 
    dstCol += 480; 
} 

Die Idee ist dst, um zu füllen und um den Zugriff auf imgData zu springen. Jeder moderne Speicher füllt sich effizienter, wenn Sie ihn in der richtigen Reihenfolge schreiben. Der Cache und der synchrone DRAM füllen normalerweise mehrere 32-Bit-Wörter gleichzeitig. Wir können die innere Schleife mit Kenntnis des L1-Cache ausrollen. Es ist entweder 32 oder 64 Bytes, die 8 oder 16 32-Bit-Pixel darstellen. Die Füllung wird ein ähnlicher Betrag sein, so können Sie die transponieren cacheable Blöcke und verarbeiten Sie alle auf einmal. Stellen Sie sich das 640x480-Bild so vor, als bestünde es aus 8 * 8-Pixel-Kacheln (minimale L1-Cache-Größe) und verarbeiten Sie diese nacheinander.

Nachdem Sie dies getan haben, können die NEON-Anweisungen einen gewissen Prozentsatz gewinnen. Die Optimierung der Lade-/Speichereinheit (die allen CPU-Einheiten gemeinsam ist) sollte jedoch der erste Schritt sein.

HINWEIS: Neon ist SIMD (einzelne Anweisung, mehrere Daten) und es zeichnet sich durch Zahlenverarbeitung der Pixel aus, um einen Rechenschub zu erzielen, indem mehrere gleichzeitig verarbeitet werden. Es gibt einige Anweisungen, die den Speicherdurchlauf optimieren, aber der zugrunde liegende Speicher ist der gleiche für die CORE-CPU-Einheiten und die SIMD/NEON-Einheiten. Es ist möglich, dass NEON einen Schub gibt, aber ich denke, es ist sinnlos, bis Sie Ihre Zugriffsreihenfolge für Ihr Speichersystem optimiert haben.

+1

Was ich sage, wird mit größeren Bildern deutlicher und weniger, wenn Sie nur kleine Blöcke verarbeiten. Beachten Sie, dass [Paul R] (http://stackoverflow.com/users/253056/paul-r) s [NEON-Link] (https://community.arm.com/thread/3002) großartige Informationen von Peter Harris für Verarbeitung einer 16x16-Kachel mit NEON. Setzen Sie dies zusammen mit den obigen Informationen und Sie werden wahrscheinlich eine fast optimale Routine haben. –

+0

Hmm, diese erste Argumentation erscheint mir im Allgemeinen nicht intuitiv - die Speicherpuffer, L1 und L2 werden immer einen Teil des Overheads von Speichern absorbieren, während, wenn die Last an L1 fehlt, Sie nichts zu tun haben, als zu warten. Indem man den Speicherpuffern erlaubt, Schreibvorgänge effizienter zu verschmelzen, indem man virtuell jede Last garantiert, die man bei L1 verpassen kann (ohne sehr sorgfältiges Prefetching), scheint das kein großer Gewinn zu sein, aber ich gebe zu, dass es so ist nicht etwas, das ich tatsächlich bewertet habe (und hängt natürlich sehr von systemspezifischen Faktoren ab). – Notlikethat

+0

@Notlikethat Ja, die Speicherpuffer arbeiten mit sequentiellen Schreibvorgängen und haben eine begrenzte Größe. L1 wird bei ersten Lesevorgängen fehlen, aber es ist mindestens 4way um mindestens 32k. Der Schritt ist nur 480 Einträge, so dass die zweite gelesene Spalte im Cache sein wird. Ich empfehle jedoch das OP es auszuprobieren. Eine definitive Antwort kann nur durch Messen gefunden werden. Mit Sicherheit wird der Tiling-Ansatz besser sein. Es hängt vom gesamten Speichersystem ab, aber ich bin ziemlich sicher, dass der ursprüngliche Algorithmus bei einer großen Klasse von CPUs mehr physische Schreibvorgänge ausführt; während der 2. wird wahrscheinlich die gleichen gelesen haben. –

Verwandte Themen