Domanda

Ho un ciclo scritto in C++ che viene eseguito per ogni elemento di una grande matrice di valori interi.All'interno del ciclo, ho la maschera di alcuni bit del numero intero e quindi trovare i valori min e max.Ho sentito dire che se io uso istruzioni SSE per queste operazioni verrà eseguito molto più velocemente rispetto ad un normale ciclo scritti usando l'and bit a bit , e condizioni if-else.La mia domanda è devo andare per queste istruzioni SSE?Inoltre, cosa succede se il mio codice viene eseguito su un processore diverso?Funzionerà ancora o queste istruzioni sono specifico del processore?

È stato utile?

Soluzione

  1. istruzioni SSE sono processore specifico. È possibile cercare il quale processore supporta la versione SSE su wikipedia.
  2. Se il codice SSE sarà più veloce o meno dipende da molti fattori: il primo è, naturalmente, se il problema è la memoria-bound o CPU-bound. Se il bus di memoria è il collo di bottiglia SSE non aiuterà molto. Provare a semplificare i calcoli interi, se questo ha il codice più veloce, è probabilmente CPU-bound, e si ha una buona possibilità di accelerare lo compongono.
  3. Essere consapevoli del fatto che la scrittura SIMD-codice è molto più difficile che scrivere C ++ - il codice, e che il codice risultante è molto più difficile da cambiare. Tenere sempre il codice C ++ fino ad oggi, si vorrà come un commento e per verificare la correttezza del codice assembler.
  4. pensare di utilizzare una libreria come l'IPP, che implementa le operazioni SIMD basso livello incontrate ottimizzate per vari processori.

Altri suggerimenti

SIMD, di cui SSE è un esempio, permette di fare la stessa operazione su più blocchi di dati. Quindi, non sarà possibile ottenere alcun vantaggio di utilizzare SSE in sostituzione dritto per le operazioni di interi, si otterrà solo vantaggi se si può fare le operazioni su più elementi di dati in una sola volta. Ciò comporta il caricamento di alcuni valori di dati contigue nella memoria, facendo il trattamento desiderato e poi lancio per la prossima serie di valori nella matrice.

I problemi:

1 Se il percorso di codice dipende dai dati che vengono elaborati, SIMD diventa molto più difficile da implementare. Ad esempio:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

Non è facile da fare come SIMD:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2 Se i dati non sono contigui, allora il caricamento dei dati nelle istruzioni SIMD è ingombrante

3 Il codice è specifico processore. SSE è solo su IA32 (Intel / AMD) e non tutto il supporto IA32 le CPU SSE.

È necessario analizzare l'algoritmo ei dati per vedere se può essere SSE'd e che richiede sapere come funziona SSE. C'è un sacco di documentazione sul sito Web di Intel.

Questo tipo di problema è un perfetto esempio di dove un buon profiler basso livello è essenziale. (Qualcosa di simile VTune) Si può dare un'idea molto più informato di dove i vostri punti caldi si trovano.

La mia ipotesi, da ciò che si descrive è che il tuo hotspot sarà probabilmente fallimenti branch prediction derivanti da min / max calcoli utilizzando if / else. Pertanto, utilizzando intrinseche SIMD dovrebbe consentire di utilizzare le istruzioni / max min, tuttavia, forse vale la pena solo cercando di utilizzare un branchless min / max caluculation invece. Questo potrebbe raggiungere la maggior parte dei guadagni con meno dolore.

Qualcosa di simile a questo:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}

Se si utilizza istruzioni SSE, si sta ovviamente limitato a processori che supportano questi. Ciò significa che x86, risalente al Pentium 2 o giù di lì (non ricordo esattamente quando sono stati introdotti, ma è molto tempo fa)

SSE2, che, per quanto posso ricordare, è quella che offre le operazioni integer, è un po 'più recente (Pentium 3? Anche se i primi processori AMD Athlon non li supportano)

In ogni caso, si hanno due opzioni per l'utilizzo di queste istruzioni. O scrivere l'intero blocco di codice in assembly (probabilmente una cattiva idea. Questo rende praticamente impossibile per il compilatore per ottimizzare il codice, ed è molto difficile per un essere umano di scrivere assembler efficiente).

In alternativa, utilizzare le intrinseche disponibili con il compilatore (se la memoria non serve, sono di solito definiti in xmmintrin.h)

Ma ancora una volta, la prestazione non può migliorare. codice SSE pone ulteriori requisiti dei dati che elabora. Soprattutto, quello da tenere a mente è che i dati devono essere allineati sui confini a 128 bit. Ci dovrebbe essere anche pochi o nessun dipendenze tra i valori caricati nella stessa registro (un registro SSE a 128 bit può contenere 4 int. L'aggiunta del primo e il secondo insieme non è ottimale. Ma l'aggiunta di tutti i quattro interi alle corrispondenti 4 int in un altro registro sarà veloce)

Si può essere tentati di utilizzare una libreria che avvolge tutto il basso livello SSE giocherellare, ma che potrebbe anche rovinare qualsiasi potenziale beneficio di prestazioni.

Non so come supporto operativo intero buon SSE è, in modo che possa anche essere un fattore che può limitare le prestazioni. SSE è principalmente mirata ad accelerare le operazioni in virgola mobile.

Se avete intenzione di utilizzare Microsoft Visual C ++, vi consigliamo di leggere questo:

http://www.codeproject.com/KB/recipes/sseintro.aspx

Abbiamo implementato un codice di elaborazione delle immagini, simile a ciò che si descrive, ma su un array di byte, in SSE. L'aumento di velocità rispetto al codice C è considerevole, a seconda dell'algoritmo esatto più di un fattore 4, anche in relazione al compilatore Intel. Tuttavia, come già accennato di avere i seguenti inconvenienti:

  • Portabilità. Il codice verrà eseguito su ogni CPU Intel-like, così anche AMD, ma non su altre CPU. Questo non è un problema per noi, perché noi controlliamo l'hardware di destinazione. Commutazione compilatori e perfino ad un sistema operativo a 64 bit può anche essere un problema.

  • Si dispone di una ripida curva di apprendimento, ma ho scoperto che dopo aver cogliere i principi di scrittura nuovi algoritmi non è così difficile.

  • Maintainability. La maggior parte dei programmatori C o C ++ non hanno alcuna conoscenza di montaggio / SSE.

Il mio consiglio è quello di andare per esso solo se si ha realmente bisogno il miglioramento delle prestazioni, e non è possibile trovare una funzione per il vostro problema in una libreria come l'Intel IPP, e se si può vivere con i problemi di portabilità .

posso dire dalla mia esperienza che SSE porta un enorme (4x e su) aumento di velocità su una versione c pianura del codice (senza asm inline, non intrinseche usati), ma assembler ottimizzato a mano può battere assembly generato dal compilatore se il compilatore non riesce a capire ciò che il programmatore previsto (belive me, compilatori non coprono tutte le possibili combinazioni di codice e non lo faranno mai). Oh, e, il compilatore non può ogni volta impaginare i dati che funziona a più rapida velocità possibile. Ma è necessario molto experince per un aumento di velocità su un processore Intel-compilatore (se possibile).

istruzioni SSE erano originariamente solo su chip Intel, ma di recente (dal Athlon?) AMD li supporta pure, quindi se si codice contro il set di istruzioni SSE, si dovrebbe essere portabile alla maggior parte dei procs x86.

Detto questo, non può valere la pena il vostro tempo per imparare la codifica SSE se non sei già familiarità con assemblatore su x86 - una scelta più facile potrebbe essere quella di controllare i documenti del compilatore e vedere se ci sono opzioni per consentire al compilatore di autogenerate codice di SSE per voi. Alcuni compilatori molto bene vettorizzazione loop in questo modo. (Probabilmente non siete sorpreso di sentire che i compilatori Intel fare un buon lavoro di questo:)

Scrivere il codice che aiuta il compilatore a capire cosa si sta facendo. GCC capirà e ottimizzare il codice SSE come questo:

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

Basta non dimenticare di avere -msse2 -msse sui vostri parametri di costruzione!

Anche se è vero che la SSE è specifico per alcuni processori (SSE può essere relativamente sicuri, SSE2 e tanto meno nella mia esperienza), è in grado di rilevare la CPU in fase di esecuzione, e caricare il codice in modo dinamico a seconda della CPU di destinazione.

intrinseche SIMD (come SSE2) possono accelerare questo genere di cose, ma fino prendere competenze per utilizzare in modo corretto. Essi sono molto sensibili all'allineamento e latenza tubazione; uso sconsiderato può fare prestazioni ancora peggio di quello che sarebbe stato senza di loro. Si otterrà un aumento di velocità molto più facile e più immediata dal semplice uso della cache prefetching per assicurarsi che tutti i vostri interi sono in L1 in tempo per voi di operare su di essi.

A meno che la vostra funzione ha bisogno di un rendimento migliore di 100.000.000 interi al secondo, SIMD probabilmente non vale la pena per voi.

Giusto per aggiungere brevemente a ciò che è stato detto prima sulle diverse versioni di SSE essendo disponibile su diverse CPU: questo può essere controllato guardando le rispettive funzionalità bandiere restituiti dal CPUID (si veda ad esempio la documentazione di Intel per i dettagli)

Date un'occhiata a assembler inline per C / C ++, ecco una DDJ articolo . A meno che non si è certi al 100% il programma verrà eseguito su una piattaforma compatibile è necessario seguire le raccomandazioni molti hanno qui riportati.

Sono d'accordo con i manifesti precedenti. I benefici possono essere molto grande, ma per farlo può richiedere un sacco di lavoro. documentazione Intel su queste istruzioni è di oltre 4K pagine. Si consiglia di controllare EasySSE (c ++ wrapper biblioteca oltre intrinseche + esempi) libero da ocali Inc.

Presumo mia affiliazione con questa EasySSE è chiaro.

Io non consiglio di farlo da soli a meno che non sei abbastanza abile con l'assemblea.Utilizzando SSE, più che probabile, richiedono un'attenta riorganizzazione dei dati, come Skizz sottolinea, e il vantaggio è spesso discutibile al meglio.

Probabilmente sarebbe molto meglio per voi di scrivere molto piccoli cicli e mantenere i vostri dati molto ben organizzato e si basa solo sul compilatore di fare questo per voi.Intel Compilatore C GCC (dal 4.1) può auto-vectorize il tuo codice, e probabilmente fare un lavoro migliore.(Basta aggiungere -ftree-vectorize al tuo CXXFLAGS.)

Modifica:Un'altra cosa che vorrei ricordare è che molti compilatori supportano assemblea intrinseci, che probabilmente, IMO, più facile da utilizzare rispetto ad asm() o __asm{} sintassi.

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