2010-09-09 15 views
6

Gibt es einen schnellen Weg, um Werte eines Schwimmers Array in C++, zu multiplizieren, um diese Funktion zu optimieren (wo count ein Vielfaches von 4):schnelle Vermehrung von Werten in einem Array

void multiply(float* values, float factor, int count) 
{ 
    for(int i=0; i < count; i++) 
    { 
     *value *= factor; 
     value++; 
    } 
} 

Es muss eine Lösung arbeiten unter Mac OS X und Windows, Intel und Nicht-Intel. Denken Sie an SSE, Vektorisierung, Compiler (gcc vs. MSVC).

+5

Sie scheinen die Antwort bereits zu kennen. Bist du in irgendeiner Weise festgefahren oder erwartest du nur, dass jemand anderes den Code für dich schreibt? –

+1

Dies ist kein Rent-a-Coder! – Skizz

+1

Wie groß soll das Array sein (> 1,> 10,> 100,> 1000,> 10000)? Sie überlegen, in Ihrem Fall die Optimierung mehrerer Threads (Threads) zu verwenden? Sind Einschränkungen bezüglich des Arrays im Voraus bekannt, andere zählen dann als Vielfaches von 4? – Suma

Antwort

2

Wenn Sie möchten, dass Ihr Code plattformübergreifend ist, dann müssen Sie entweder plattformunabhängigen Code schreiben oder Sie müssen eine Last von #ifdef s schreiben.

Haben Sie versucht, eine manuelle Loop-Abrollung auszuprobieren, und zu sehen, ob es einen Unterschied macht?

2

Da Sie wissen, die count ein Vielfaches von 4, können Sie Ihre Schleife abrollen kann ...

void multiply(float* values, float factor, int count) 
{ 
    count = count >> 2; // count/4 
    for(int i=0; i < count ; i++) 
    { 
     *value *= factor; 
     *(value+1) *= factor; 
     *(value+2) *= factor; 
     *(value+3) *= factor; 
     value += 4; 
    } 
} 
+0

Dies wird fast sicher nicht schneller sein, da es die gleiche Menge an Multiplikationen mit komplexerer Zeigerarithmetik als das Original ausführt. Ich wäre an Ihren Messungen interessiert, um zu zeigen, dass dies eine Verbesserung ist. –

+2

GCC tut dies mit '-Funroll-Loops'. –

+0

@Steve: Dies könnte einen Unterschied machen, abhängig davon, wie gut der Compiler bereits ist (und wie gut der Verzweigungsprädiktor der CPU ist). Das Verhältnis von Multiplikationen zu bedingten Verzweigungen ist von 1: 1 auf 4: 1 angestiegen. –

2

Haftungsausschluss: Offensichtlich wird dies nicht auf dem iPhone funktionieren, iPad, Android, oder ihre Zukunft Äquivalente .

#include <mmintrin.h> 
#include <xmmintrin.h> 

__m128 factor4 = _mm_set1_ps(factor); 
for (int i=0; i+3 < count; i += 4) 
{ 
    __m128 data = _mm_mul_ps(_mm_loadu_ps(values), factor4); 
    _mm_storeu_ps(values, data); 
    values += 4; 
} 
for (int i=(count/4)*4; i < count; i++) 
{ 
    *values *= factor; 
    value++; 
} 
+0

wird es auf x86 Android arbeiten –

2

Haben Sie an OpenMP gedacht?

Die meisten modernen Computer haben Multi-Core-CPUs und fast jeder größere Compiler scheint OpenMP eingebaut zu haben. Sie gewinnen Geschwindigkeit um fast jeden Preis.

Siehe Wikipedia's article on OpenMP.

0

Die beste Lösung ist, es einfach zu halten und den Compiler für Sie optimieren zu lassen. GCC weiß über SSE, SSE2, altivec und was noch. Wenn Ihr Code zu komplex ist, kann Ihr Compiler ihn nicht für jedes mögliche Ziel optimieren.

0

Wie Sie bereits erwähnt haben, gibt es zahlreiche Architekturen mit SIMD-Erweiterungen und SIMD ist wahrscheinlich die beste Wahl, wenn es um die Optimierung geht. Sie sind alle jedoch plattformspezifisch und die Sprachen C und C++ sind nicht SIMD-freundlich.

Das erste, was Sie jedoch versuchen sollten, ist die SIMD-spezifischen Flags für Ihren Build zu aktivieren. Der Compiler kann Muster erkennen, die mit SIMD optimiert werden können.

Die nächste Sache ist, plattformspezifischen SIMD-Code mit Compiler-Intrinsik oder Assembly zu schreiben, wo es angebracht ist. Sie sollten jedoch eine portable Nicht-SIMD-Implementierung für Plattformen beibehalten, die keine optimierte Version haben. #ifdef s aktivieren SIMD auf Plattformen, die es unterstützen.

Schließlich, zumindest auf ARM, aber nicht sicher auf Intel, beachten Sie, dass kleinere Ganzzahl und Fließkommatypen eine größere Anzahl von parallelen Operationen pro einzelnen SIMD-Befehl erlauben.

0

Ich denke, es gibt nicht viel, was Sie tun können, macht einen großen Unterschied. Vielleicht können Sie es mit OpenMP oder SSE etwas beschleunigen. Aber moderne CPUs sind schon ziemlich schnell. In einigen Anwendungen ist Speicherbandbreite/Latenz tatsächlich der Flaschenhals und es wird schlechter. Wir haben bereits drei Cache-Ebenen und benötigen intelligente Prefetch-Algorithmen, um große Verzögerungen zu vermeiden. Es macht also Sinn, auch über Speicherzugriffsmuster nachzudenken.Zum Beispiel, wenn Sie bei der Implementierung eines solchen multiply und eine add und es wie folgt verwendet werden:

void multiply(float vec[], float factor, int size) 
{ 
    for (int i=0; i<size; ++i) 
    vec[i] *= factor; 
} 

void add(float vec[], float summand, int size) 
{ 
    for (int i=0; i<size; ++i) 
    vec[i] += summand; 
} 

void foo(float vec[], int size) 
{ 
    multiply(vec,2.f,size); 
    add(vec,9.f,size); 
} 

Sie grundsätzlich zweimal über den Speicherblock vorbei. Abhängig von der Größe des Vektors passt er möglicherweise nicht in den L1-Cache. In diesem Fall wird bei einer zweimaligen Übergabe zusätzliche Zeit benötigt. Dies ist offensichtlich schlecht und Sie sollten versuchen, Speicherzugriffe "lokal" zu halten. In diesem Fall wird eine einzelne Schleife wahrscheinlich schneller sein. Als Faustregel gilt: Versuchen Sie, linear auf den Speicher zuzugreifen, und versuchen Sie, "lokal" auf den Speicher zuzugreifen, indem Sie versuchen, die bereits im L1-Cache vorhandenen Daten wiederzuverwenden. Nur eine Idee.

Verwandte Themen