Optimieren von C-Code mit SSE-Intrinsics

8

Ich habe eine Weile mit der Leistung der Netzwerkcodierung in einer Anwendung gekämpft, die ich entwickle (siehe Optimierung des SSE-Codes , Verbessern der Leistung der Codierung des Netzwerkcodes und OpenCL-Verteilung ). Jetzt bin ich ziemlich nah dran, eine akzeptable Leistung zu erreichen. Dies ist der aktuelle Zustand der innersten Schleife (in der & gt; 99% der Ausführungszeit ausgegeben wird):

%Vor%

Haben Sie Vorschläge, wie Sie dies weiter optimieren können? Ich verstehe, dass es schwierig ist, ohne Kontext auszukommen, aber jede Hilfe wird geschätzt!

    
Yrlec 22.10.2011, 16:08
quelle

2 Antworten

8

Jetzt, wo ich wach bin, hier ist meine Antwort:

In Ihrem ursprünglichen Code ist der Engpass fast sicher _mm_set_epi32 . Dieser einzelne intrinsische wird in diesem Chaos in Ihrer Versammlung kompiliert:

%Vor%

Was ist das? 9 Anweisungen?!?!?! Pure Overhead ...

Eine andere Stelle, die seltsam erscheint, ist, dass der Compiler die Adds und Loads nicht zusammengeführt hat:

%Vor%

sollte in Folgendes eingefügt worden sein:

%Vor%

Ich bin mir nicht sicher, ob der Compiler hirntot wurde, oder ob er tatsächlich einen legitimen Grund hatte, das zu tun ... Wie auch immer, es ist eine kleine Sache im Vergleich zu _mm_set_epi32 .

Haftungsausschluss: Der Code, den ich hier vorstellen werde, verstößt gegen das strikte Aliasing. Häufig werden jedoch nicht standardkonforme Methoden benötigt, um eine maximale Leistung zu erzielen.

Lösung 1: Keine Vektorisierung

Diese Lösung setzt voraus, dass allZero wirklich nur aus Nullen besteht.

Die Schleife ist eigentlich einfacher als es aussieht. Da es nicht viel Arithmetik gibt, ist es besser, einfach nicht zu vektorisieren:

%Vor%

Was kompiliert zu diesem auf x64:

%Vor%

und das auf x86:

%Vor%

Es ist möglich, dass beide bereits schneller sind als Ihr ursprünglicher (SSE) Code ... Auf x64 wird das Abrollen noch besser.

Lösung 2: SSE2 Integer Shuffle

Diese Lösung entrollt die Schleife auf zwei Iterationen:

%Vor%

was zu diesem (x86) kompiliert wird: (x64 ist nicht zu verschieden)

%Vor%

Nur etwas länger als die nicht vektorisierte Version für zwei Iterationen. Dies verwendet sehr wenige Register, so dass Sie diese sogar auf x86 weiter ausrollen können.

Erläuterungen:

  • Wie bereits von Paul R. erwähnt, ermöglicht das Abrollen auf zwei Iterationen die Kombination der Anfangslast in einer SSE-Last. Dies hat auch den Vorteil, dass Ihre Daten in die SSE-Register gelangen.
  • Da die Daten in den SSE-Registern beginnen, kann _mm_set_epi32 (das in etwa ~ 9 Anweisungen in Ihrem ursprünglichen Code kompiliert wird) durch ein einzelnes _mm_shuffle_epi32 ersetzt werden.
Mysticial 25.10.2011, 00:00
quelle
4

Ich schlage vor, dass Sie Ihre Schleife um einen Faktor von 2 auflösen, so dass Sie 4 messageField-Werte mit einem _mm_load_XXX laden können, und entpacken Sie dann diese vier Werte in zwei Vektorpaare und verarbeiten Sie sie gemäß der aktuellen Schleife. Auf diese Weise wird nicht viel unordentlicher Code vom Compiler für _mm_set_epi32 generiert und alle Ihre Ladevorgänge und Speicher werden 128 Bit SSE-Lade- / Speichervorgänge sein. Dies gibt dem Compiler auch mehr Gelegenheit, Befehle innerhalb der Schleife optimal zu planen.

    
Paul R 24.10.2011 13:28
quelle

Tags und Links