2010-08-01 12 views

risposta

3
max(i,j) - min(i,j) 
(i>j)*(i-j) + (j>i)*(j-i) 

si può certamente utilizzare SSE registra, ma compilatore può fare questo per voi in ogni modo

8

Ci sono diversi modi per farlo, mi limiterò a citare uno:

SSE4

  • Utilizzare PMINUD e PMAXUD per separare il valore maggiore nel registro # 1 e il valore minore nel registro # 2.
  • Sottrarli.

MMX/SSE2

  • flip il bit di segno dei due valori, perché l'istruzione successiva solo accetta firmato confronto intero.
  • PCMPGTD. Usa questo risultato come maschera.
  • Calcolare i risultati di entrambi (a-b) e (b-a)
  • Usa POR (PAND (mask, a-b), PANDN (mask, b-a)) per selezionare il valore corretto per la differenza assoluta.
1

Prova questo (presuppone 2 ° complementi, che è judgning OK dal fatto che stai chiedendo SSE):

int d = a-b; 
int ad = ((d >> 30) | 1) * d; 

Spiegazione: segno-bit (bit 31) viene propagato fino al 1 ° po. il | 1 parte assicura che il moltiplicatore sia 1 o -1. Le moltiplicazioni sono veloci sulle moderne CPU.

+2

Ma il bit di segno di A-B non è un'indicazione che a greggo

2

calcolare la differenza e riportare il valore assoluto

__m128i diff = _mm_sub_epi32(a, b); 
__m128i mask = _mm_xor_si128(diff, a); 
mask = _mm_xor_si128(mask, b); 
mask = _mm_srai_epi32(mask, 31); 
diff = _mm_xor_si128(diff, mask); 
mask = _mm_srli_epi32(mask, 31); 
diff = _mm_add_epi32(diff, mask); 

Ciò richiede uno meno che usando l'operazione firmato confrontare op, e produce meno pressione registro.

Stessa quantità di pressione di registro come prima, 2 ulteriori operazioni, ramo migliore e unione di catene di dipendenze, associazione delle istruzioni per la decodifica di uops e utilizzo dell'unità separato. Anche se questo richiede un carico, che potrebbe essere esaurito nella cache. Sono fuori di idee dopo questo.

__m128i mask, diff; 
diff = _mm_set1_epi32(-1<<31); // dependency branch after 
a = _mm_add_epi32(a, diff); // arithmetic sign flip 
b = _mm_xor_si128(b, diff); // bitwise sign flip parallel with 'add' unit 
diff = _mm_xor_si128(a, b); // reduce uops, instruction already decoded 
mask = _mm_cmpgt_epi32(b, a); // parallel with xor 
mask = _mm_and_si128(mask, diff); // dependency merge, branch after 
a = _mm_xor_si128(a, mask); // if 2 'bit' units in CPU, parallel with next 
b = _mm_xor_si128(b, mask); // reduce uops, instruction already decoded 
diff = _mm_sub_epi32(a, b); // result 

Dopo aver cronometrato ogni versione con 2 milioni di iterazioni su un Core2Duo, le differenze sono incommensurabili. Quindi scegli ciò che è più facile da capire.

+0

Si suppone che 'sum' sia' diff'? Bah, ora che ho letto il tuo da vicino è abbastanza simile al mio. Ma più intelligente, gentile nell'usare la differenza firmata come un confronto firmato. Tuttavia, il confronto con lo zero è generalmente più leggero rispetto allo spostamento verso destra. – Potatoswatter

+0

In realtà, abbiamo entrambi commesso un errore: nella prima funzione è necessaria una funzione di consenso a tre input, non XOR a tre vie. – Potatoswatter

2

SSE2:

sembra essere circa la stessa velocità come seconda funzione di Phernost. A volte GCC lo pianifica per essere un ciclo completo più veloce, altre volte un po 'più lento.

__m128i big = _mm_set_epi32(INT_MIN, INT_MIN, INT_MIN, INT_MIN); 

a = _mm_add_epi32(a, big); // re-center the variables: send 0 to INT_MIN, 
b = _mm_add_epi32(b, big); // INT_MAX to -1, etc. 
__m128i diff = _mm_sub_epi32(a, b); // get signed difference 
__m128i mask = _mm_cmpgt_epi32(b, a); // mask: need to negate difference? 
mask = _mm_andnot_si128(big, mask); // mask = 0x7ffff... if negating 
diff = _mm_xor_si128(diff, mask); // 1's complement except MSB 
diff = _mm_sub_epi32(diff, mask); // add 1 and restore MSB 

SSSE3:

sempre in modo leggermente più veloce del precedente. Vi sono molte variazioni a seconda di come vengono dichiarate le cose al di fuori del ciclo. (Ad esempio, rendendo a e bvolatile rende le cose più veloci! Sembra essere un effetto casuale sulla pianificazione.) Ma questo è costantemente più veloce di un ciclo o così.

__m128i big = _mm_set_epi32(INT_MIN, INT_MIN, INT_MIN, INT_MIN); 

a = _mm_add_epi32(a, big); // re-center the variables: send 0 to INT_MIN, 
b = _mm_add_epi32(b, big); // INT_MAX to -1, etc. 
__m128i diff = _mm_sub_epi32(b, a); // get reverse signed difference 
__m128i mask = _mm_cmpgt_epi32(b, a); // mask: need to negate difference? 
mask = _mm_xor_si128(mask, big); // mask cannot be 0 for PSIGND insn 
diff = _mm_sign_epi32(diff, mask); // negate diff if needed 

SSE4 (thx rwong):

Non è possibile testare questo.

__m128i diff = _mm_sub_epi32(_mm_max_epu32(a, b), _mm_min_epu32(a, b)); 
0

Ehm ... la sua abbastanza facile ...

int diff = abs(a - b); 

facilmente vectorisable (Uso SSE3 come):

__m128i sseDiff = _mm_abs_epi32(_mm_sub_epi32(a, b)); 

a e b sono numeri interi senza segno. Considera a = 0 eb = 0xffffffff. La differenza assoluta corretta è 0xffffffff, ma la soluzione fornirà 1.

+0

Come spiegato dalla strana modifica, questo è sbagliato. Un altro esempio per interi senza segno a 8 bit: per '4 - 255', la differenza assoluta corretta è 251.Ma trattandolo come firmato -5 e prendendo il valore assoluto si ottiene 5, che è la stessa risposta che si ottiene per 255-250. –

3

A partire da tommesani.com, una soluzione per questo problema consiste nell'utilizzare due volte la sottrazione senza segno.

Come la sottrazione saturazione non va mai sotto lo 0, si calcola: r1 = (ab) .saturating r2 = (ba) .saturating

Se a è maggiore di b, r1 conterrà la risposta, e r2 sarà 0 e viceversa per b> a. OR i due risultati parziali insieme daranno il risultato desiderato.

In base a the VTUNE users manual, PSUBUSB/PSUBUSW è disponibile per i registri a 128 bit, quindi è necessario essere in grado di ottenere un sacco di parallelizzazione in questo modo.

+0

sub con saturazione non firmata sembra essere disponibile solo per parole o byte, ma fortunatamente è quello che stavo cercando per. Questa risposta è leggermente migliore rispetto alla risposta più votata usando SSE4.1 PMINU/PMAXU/PSUB, perché 'POR' può funzionare su più porte di' PSUB' su alcune CPU (Intel Haswell ad esempio). –

0

Uno o più dei seguenti risultati generano codice senza ramo, a seconda della macchina e del compilatore, poiché le espressioni condizionali sono tutte molto semplici.

Non ho ricevuto tutte le risposte di sse ma probabilmente alcuni dei seguenti sono rappresentati nel codice vettoriale; certamente tutti i sotto sono vettorizzabili (se si ha il paragone senza segno per cominciare, o si falsi passando prima al msb). Ho pensato che sarebbe stato utile avere delle risposte scalari pratiche alla domanda.

unsigned udiff(unsigned a, unsigned b) 
{ 
     unsigned result = a-b; // ok if a<b; 
     if(a <b) result = -result; 
     return result; 
} 
unsigned udiff(unsigned a, unsigned b) 
{ 
     unsigned n =(a<b)? (unsigned)-1 : 0u; 
     unsigned result = a-b; 
     return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF 
} 


unsigned udiff(unsigned a, unsigned b) 
{ 
     unsigned axb = a^b; 
     if(a < b) axb = 0; 
     return (axb^b) - (axb^a); // a-b, or b-a 
} 

Ciò funziona su x86_64 (o qualcosa in cui temps a 64 bit sono fondamentalmente liberi)

unsigned udiff(unsigned a, unsigned b) 
{ 
     unsigned n= (unsigned)( 
     (long long)((unsigned long long)a - (unsigned long long)b)>>32 
        ); // same n as 2nd example 
     unsigned result = a-b; 
     return (result^n)-n; // 'result' if n = 0; '-result' if n = 0xFFFFFFFF 
} 
Problemi correlati