Domanda

Esiste qualche differenza tra gli intrinseci logici SSE per i diversi tipi?Ad esempio, se prendiamo l'operazione OR, ci sono tre intrinseci:_mm_or_ps, _mm_or_pd e _mm_or_si128 che fanno tutti la stessa cosa:calcolare bit per bit OR dei loro operandi.Le mie domande:

  1. C'è qualche differenza tra l'utilizzo dell'uno o dell'altro intrinseco (con il casting del tipo appropriato).Non ci saranno costi nascosti come un'esecuzione più lunga in alcune situazioni specifiche?

  2. Questi intrinseci si associano a tre diverse istruzioni x86 (por, orps, orpd).Qualcuno ha idea del motivo per cui Intel sta sprecando prezioso spazio nel codice operativo per diverse istruzioni che fanno la stessa cosa?

È stato utile?

Soluzione

Penso che tutti e tre siano effettivamente uguali, cioè operazioni a 128 bit bit. La ragione per cui esistono forme diverse è probabilmente storico, ma non sono sicuro. Immagino che lo sia possibile Che potrebbe esserci qualche comportamento aggiuntivo nelle versioni di punta mobile, ad esempio quando ci sono NAN, ma questa è pura ipotesi. Per gli ingressi normali le istruzioni sembrano essere intercambiabili, ad esempio

#include <stdio.h>
#include <emmintrin.h>
#include <pmmintrin.h>
#include <xmmintrin.h>

int main(void)
{
    __m128i a = _mm_set1_epi32(1);
    __m128i b = _mm_set1_epi32(2);
    __m128i c = _mm_or_si128(a, b);

    __m128 x = _mm_set1_ps(1.25f);
    __m128 y = _mm_set1_ps(1.5f);
    __m128 z = _mm_or_ps(x, y);

    printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
    printf("x = %vf, y = %vf, z = %vf\n", x, y, z);

    c = (__m128i)_mm_or_ps((__m128)a, (__m128)b);
    z = (__m128)_mm_or_si128((__m128i)x, (__m128i)y);

    printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
    printf("x = %vf, y = %vf, z = %vf\n", x, y, z);

    return 0;
}

$ gcc -Wall -msse3 por.c -o por

$ ./por

a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000
a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000

Altri suggerimenti

  1. C'è qualche differenza tra l'utilizzo dell'uno o dell'altro intrinseco (con il casting del tipo appropriato).Non ci saranno costi nascosti come un'esecuzione più lunga in alcune situazioni specifiche?

Sì, ci possono essere ragioni di prestazioni per sceglierne uno vs.l'altro.

1: A volte c'è uno o due cicli aggiuntivi di latenza (ritardo di inoltro) se l'output di un'unità di esecuzione intera deve essere instradato all'input di un'unità di esecuzione FP o viceversa.Ci vogliono MOLTI cavi per spostare 128b di dati verso una qualsiasi delle tante destinazioni possibili, quindi i progettisti di CPU devono fare dei compromessi, come avere solo un percorso diretto da ogni output FP a ogni input FP, non a TUTTI i possibili input.

Vedere questa risposta, O La microarchitettura di Agner Fog doc per ritardi di bypass.Cerca "Ritardi di bypass dei dati su Nehalem" nel documento di Agner;contiene alcuni buoni esempi pratici e discussioni.Ha una sezione per ogni microarco che ha analizzato.

Tuttavia, i ritardi per il passaggio dei dati tra i diversi domini o i diversi tipi di registri sono più piccoli sul ponte sabbioso e sul ponte Ivy rispetto al Nealem e spesso zero.-- Doc a micro arco di Agner Fog

Ricorda che la latenza non ha importanza se non si trova nel percorso critico del tuo codice.Utilizzando pshufd invece di movaps + shufps può essere una vittoria se il throughput uop è il collo di bottiglia, piuttosto che la latenza del percorso critico.

2: IL ...ps richiede 1 byte di codice in meno rispetto alle altre due.Ciò allineerà le seguenti istruzioni in modo diverso, il che può avere importanza per i decodificatori e/o le linee della cache uop.

3: Le recenti CPU Intel possono eseguire solo le versioni FP sulla porta 5.

  • Merom (Core2) e Penryn: orps può essere eseguito su p0/p1/p5, ma solo su dominio intero.Presumibilmente tutte e 3 le versioni sono state decodificate nello stesso identico uop.Quindi si verifica il ritardo dell'inoltro tra domini.(Anche le CPU AMD fanno questo:Le istruzioni bit a bit FP vengono eseguite nel dominio ivec.)

  • Nehalem / Sandybridge / IvB / Haswell / Broadwell: por può essere eseguito su p0/p1/p5, ma orps può essere eseguito solo sulla porta 5.p5 è necessario anche per gli shuffle, ma le unità FMA, FP add e FP mul sono sulle porte 0/1.

  • Lago Celeste: por E orps entrambi hanno una produttività di 3 per ciclo.Le informazioni sui ritardi di inoltro non sono ancora disponibili.

Si noti che su SnB/IvB (AVX ma non AVX2), solo p5 deve gestire le operazioni logiche 256b, poiché vpor ymm, ymm richiede AVX2.Probabilmente non era questo il motivo del cambiamento, dal momento che Nehalem lo ha fatto.

Come scegliere saggiamente:

Se il throughput operativo logico sulla porta 5 potrebbe rappresentare un collo di bottiglia, utilizzare le versioni intere, anche sui dati FP.Ciò è particolarmente vero se si desidera utilizzare mescolamenti di numeri interi o altre istruzioni di spostamento dei dati.

Le CPU AMD utilizzano sempre il dominio intero per la logica, quindi se hai più cose da fare sul dominio intero, eseguile tutte contemporaneamente per ridurre al minimo i viaggi di andata e ritorno tra i domini.Latenza più brevi elimineranno le cose dal buffer di riordino più velocemente, anche se una catena di distribuzione non rappresenta il collo di bottiglia per il tuo codice.

Se vuoi semplicemente impostare/cancellare/invertire un po' i vettori FP tra le istruzioni FP add e mul, usa il comando ...ps logiche, anche su dati a doppia precisione, perché singolo e doppio FP sono lo stesso dominio su ogni CPU esistente, e il ...ps le versioni sono più corte di un byte.

Ci sono ragioni pratiche/fattore umano per utilizzare il file ...pd versioni, tuttavia, che spesso superano il risparmio di 1 byte di codice.La leggibilità del tuo codice da parte di altri esseri umani è un fattore:Si chiederanno perché tratti i tuoi dati come singoli quando in realtà sono doppi.Specialecon elementi intrinseci C/C++, riempiendo il tuo codice di cast intermedi __mm256 E __mm256d non ne vale la pena.Se la sintonizzazione a livello di allineamento insn è importante, scrivi direttamente in asm, non intrinseci!(Avere l'istruzione un byte più lunga potrebbe allineare meglio le cose per la densità della linea della cache uop e/o i decodificatori.)

Per i dati interi, utilizzare le versioni intere.Salvare un byte di istruzione non vale il ritardo di bypass e il codice intero spesso mantiene la porta5 completamente occupata con i mescolamenti.Per Haswell, molte istruzioni di mescolamento/inserimento/estrazione/compressione/decompressione sono diventate solo p5, invece di p1/p5 per SnB/IvB.

  1. Questi intrinseci si associano a tre diverse istruzioni x86 (por, orps, orpd).Qualcuno ha qualche idea per cui Intel sta sprecando uno spazio prezioso per il codice opzionale per diverse istruzioni che fanno la stessa cosa?

Se guardi la storia di questi set di istruzioni, puoi vedere come siamo arrivati ​​​​qui.

por  (MMX):     0F EB /r
orps (SSE):     0F 56 /r
orpd (SSE2): 66 0F 56 /r
por  (SSE2): 66 0F EB /r

MMX esisteva prima di SSE, quindi sembra che i codici operativi per SSE (...ps) le istruzioni sono state scelte dalle stesse 0F xx spazio.Quindi per SSE2, il ...pd versione aggiunta a 66 prefisso della dimensione dell'operando al ...ps opcode e la versione intera ha aggiunto a 66 prefisso alla versione MMX.

Essi Potevo hanno lasciato fuori orpd e/o por, ma non lo fecero.Forse pensavano che i futuri progetti di CPU avrebbero potuto avere percorsi di inoltro più lunghi tra domini diversi, e quindi utilizzare le istruzioni di corrispondenza per i tuoi dati sarebbe stato un problema più grande.Anche se esistono codici operativi separati, AMD e la prima Intel li trattavano tutti allo stesso modo, come int-vettoriali.

Secondo le linee guida di ottimizzazione Intel e AMD la miscelazione dei tipi di OP con tipi di dati produce un colpo di prestazione mentre la CPU etichetta internamente le metà di 64 bit del registro per un particolare tipo di dati. Questo sembra effettuare principalmente il rivestimento dei tubi poiché l'istruzione viene decodificata e gli UOP sono programmati. Funzionalmente producono lo stesso risultato. Le versioni più recenti per i tipi di dati interi hanno una codifica maggiore e occupano più spazio nel segmento di codice. Quindi, se la dimensione del codice è un problema, utilizzare le vecchie OP in quanto questi hanno una codifica più piccola.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top