2015-06-03 8 views
7

Ich möchte SIMD minmag and maxmag functions implementieren. Soweit ich verstehe, sind diese FunktionenSIMD minmag und maxmag

minmag(a,b) = |a|<|b| ? a : b 
maxmag(a,b) = |a|>|b| ? a : b 

Ich möchte diese für Float und Double und meine Zielhardware ist Haswell. Was ich wirklich brauche, ist Code, der beides berechnet. Hier ist, was ich für SSE4.1- für Doppel (der AVX-Code ist fast identisch)

static inline void maxminmag(__m128d & a, __m128d & b) { 
    __m128d mask = _mm_castsi128_pd(_mm_setr_epi32(-1,0x7FFFFFFF,-1,0x7FFFFFFF)); 
    __m128d aa  = _mm_and_pd(a,mask); 
    __m128d ab  = _mm_and_pd(b,mask); 
    __m128d cmp  = _mm_cmple_pd(ab,aa); 
    __m128d cmpi = _mm_xor_pd(cmp, _mm_castsi128_pd(_mm_set1_epi32(-1))); 
    __m128d minmag = _mm_blendv_pd(a, b, cmp); 
    __m128d maxmag = _mm_blendv_pd(a, b, cmpi); 
    a = maxmag, b = minmag; 
} 

Dies ist jedoch nicht so effizient ist wie Ich mag würde. Gibt es eine bessere Methode oder zumindest eine denkwürdige Alternative? Ich möchte versuchen, Port 1 zu vermeiden, da ich bereits viele Additionen/Subtraktionen über diesen Port habe. Die _mm_cmple_pd Instrinsic geht an Port 1.

Die Hauptfunktion ich interessiert bin, ist dies:

//given |a| > |b| 
static inline doubledouble4 quick_two_sum(const double4 & a, const double4 & b) { 
    double4 s = a + b; 
    double4 e = b - (s - a); 
    return (doubledouble4){s, e}; 
} 

Also, was ich wirklich bin nach ist diese

static inline doubledouble4 two_sum_MinMax(const double4 & a, const double4 & b) { 
    maxminmag(a,b);  
    return quick_to_sum(a,b); 
} 

Edit: Mein Ziel ist es für two_sum_MinMax schneller sein als two_sum unter:

static inline doubledouble4 two_sum(const double4 &a, const double4 &b) { 
     double4 s = a + b; 
     double4 v = s - a; 
     double4 e = (a - (s - v)) + (b - v); 
     return (doubledouble4){s, e}; 
} 

Edit: Hier ist die ultimative Funktion, nach der ich suche. Es fügt 20 Add/Subs hinzu, die alle an Port 1 von Haswell gehen. Wenn ich meine Implementierung von two_sum_MinMax in dieser Frage verwende, wird sie auf 16 Add/Subs auf Port 1 heruntergesetzt, aber sie hat eine schlechtere Latenz und ist noch langsamer. Sie können die Baugruppe für diese Funktion sehen und erfahren Sie mehr darüber, warum ich über diese Pflege zu optimize-for-fast-multiplication-but-slow-addition-fma-and-doubledouble

static inline doublefloat4 adddd(const doubledouble4 &a, const doubledouble4 &b) { 
     doubledouble4 s, t; 
     s = two_sum(a.hi, b.hi); 
     t = two_sum(a.lo, b.lo); 
     s.lo += t.hi; 
     s = quick_two_sum(s.hi, s.lo); 
     s.lo += t.lo; 
     s = quick_two_sum(s.hi, s.lo); 
     return s; 
     // 2*two_sum, 2 add, 2*quick_two_sum = 2*6 + 2 + 2*3 = 20 add 
} 
+1

mich korrigieren, wenn ich falsch, aber würde nicht 'minmag = blendv (a, b, cmp); maxmag = blendv (b, a, cmp); "tust du dasselbe wie deinen Code, während du dieselbe Maske wieder verwendest? – EOF

+0

@EOF, es ist 'maxmag = _mm_blendv_pd (a, b, cmpi);' vielleicht hätte ich es 'icmp' anstelle von' cmpi' nennen sollen. Das "i" für invertieren. –

+1

Ja, mir ist klar. Aber du kannst * auch * die Mischung invertieren, indem du die Argumente umkehrst ... – EOF

Antwort

7

Hier ist eine alternative Implementierung, die weniger Befehle verwendet:

static inline void maxminmag_test(__m128d & a, __m128d & b) { 
    __m128d cmp  = _mm_add_pd(a, b); // test for mean(a, b) >= 0 
    __m128d amin = _mm_min_pd(a, b); 
    __m128d amax = _mm_max_pd(a, b); 
    __m128d minmag = _mm_blendv_pd(amin, amax, cmp); 
    __m128d maxmag = _mm_blendv_pd(amax, amin, cmp); 
    a = maxmag, b = minmag; 
} 

Es verwendet einen etwas subtiler Algorithmus (siehe unten), kombiniert mit der Tatsache, dass wir das Vorzeichenbit als Auswahlmaske verwenden können.

Es verwendet auch den @ EOF-Vorschlag, nur eine Maske zu verwenden und die Operandenreihenfolge zu wechseln, wodurch eine Anweisung gespeichert wird.

Ich habe es mit einer kleinen Anzahl von Fällen getestet und es scheint zu Ihrer ursprünglichen Implementierung zu entsprechen.


Algorithmus:

if (mean(a, b) >= 0)  // this can just be reduced to (a + b) >= 0 
{ 
    minmag = min(a, b); 
    maxmag = max(a, b); 
} 
else 
{ 
    minmag = max(a, b); 
    maxmag = min(a, b); 
} 
+1

Das ist eine clevere Lösung. Ich wollte die Min/Max-Anweisungen verwenden, dachte aber nicht darüber nach. Vielen Dank. Ich werde es versuchen und zu dir zurückkommen. –

+0

Wissen Sie, welche Ports min und max benutzen? Ich muss drei Add/Sub-Anweisungen zu schlagen. Ihre Lösung verwendet bereits ein Add, wenn min und max immer noch Port 1 verwenden, bin ich mir nicht sicher, ob es besser ist (aber es ist sicherlich einen Versuch wert). –

+0

Ich habe am Ende meiner Frage die Funktion 'two_sum' hinzugefügt, die ich durch' two_sum_MaxMin' ersetzen soll, wenn es schneller ist. –