2009-11-11 15 views
29

Sto eseguendo l'elaborazione delle immagini in C che richiede di copiare grandi quantità di dati intorno alla memoria - la sorgente e la destinazione non si sovrappongono mai.Memcpia molto veloce per l'elaborazione delle immagini?

Qual è il modo più veloce per eseguire questa operazione sulla piattaforma x86 utilizzando GCC (dove sono disponibili SSE, SSE2 ma NON SSE3)?

Mi aspetto che la soluzione sia in assembly o utilizzando gli elementi intrinseci di GCC?

ho trovato il seguente link, ma non hanno alcuna idea se è il modo migliore per andare su di esso (l'autore dice anche che ha qualche bug): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html

EDIT: si noti che una copia è necessario, non posso andare in giro dovendo copiare i dati (posso spiegare perché ma ti risparmierò la spiegazione :))

+0

puoi scrivere il tuo codice in modo che la copia non sia richiesta in primo luogo? – Ron

+0

Ron, no, non posso :( – horseyguy

+1

Se riesci a ottenere una sospensione del compilatore Intel potresti avere migliori possibilità di conversione dell'ottimizzatore in istruzioni per la cpu vettoriale –

risposta

38

Per gentile concessione di William Chan e Google. 30-70% più veloce rispetto memcpy in Microsoft Visual Studio 2005.

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size) 
{ 

    __asm 
    { 
    mov esi, src; //src pointer 
    mov edi, dest; //dest pointer 

    mov ebx, size; //ebx is our counter 
    shr ebx, 7;  //divide by 128 (8 * 128bit registers) 


    loop_copy: 
     prefetchnta 128[ESI]; //SSE2 prefetch 
     prefetchnta 160[ESI]; 
     prefetchnta 192[ESI]; 
     prefetchnta 224[ESI]; 

     movdqa xmm0, 0[ESI]; //move data from src to registers 
     movdqa xmm1, 16[ESI]; 
     movdqa xmm2, 32[ESI]; 
     movdqa xmm3, 48[ESI]; 
     movdqa xmm4, 64[ESI]; 
     movdqa xmm5, 80[ESI]; 
     movdqa xmm6, 96[ESI]; 
     movdqa xmm7, 112[ESI]; 

     movntdq 0[EDI], xmm0; //move data from registers to dest 
     movntdq 16[EDI], xmm1; 
     movntdq 32[EDI], xmm2; 
     movntdq 48[EDI], xmm3; 
     movntdq 64[EDI], xmm4; 
     movntdq 80[EDI], xmm5; 
     movntdq 96[EDI], xmm6; 
     movntdq 112[EDI], xmm7; 

     add esi, 128; 
     add edi, 128; 
     dec ebx; 

     jnz loop_copy; //loop please 
    loop_copy_end: 
    } 
} 

Si può essere in grado di ottimizzare ulteriormente a seconda della situazione esatta e qualsiasi ipotesi si è in grado di fare.

Si consiglia inoltre di controllare la fonte memcpy (memcpy.asm) e rimuovere la gestione caso speciale. Potrebbe essere possibile ottimizzare ulteriormente!

+6

Nota: le prestazioni di questo memcopy dipenderanno selvaggiamente dalla quantità di dati da copiare e dalla dimensione della cache. Ad esempio, i prefetch e le mosse non temporali possono impantanare le prestazioni per copie più piccole (adattandosi a L2) rispetto ai normali movdqa. –

+2

balaustra: non dimenticarti di mandargli per posta che hai usato il suo codice nel tuo progetto;) [http://williamchan.ca/portfolio/assembly/ssememcpy/source/viewsource.php?id=readme.txt] – ardsrk

+3

Ricordo leggere prima questo codice in un manuale AMD64. E il codice non è ottimale su Intel, dove ha problemi di aliasing della cache della banca. – hirschhornsalz

2

Se sei su Windows, usa le API DirectX, che ha specifiche routine GPU-ottimizzate per la gestione della grafica (quanto velocemente potrebbe essere? La tua CPU non è caricata. Fai qualcos'altro mentre la GPU la mastica).

Se si desidera essere indipendenti dal sistema operativo, provare OpenGL.

Non smettere di armeggiare con l'assemblatore, perché è troppo probabile che fallirai miseramente per superare gli ingegneri del software di 10 anni e più esperti nella creazione di librerie.

+1

ho bisogno che sia eseguito in MEMORY, cioè, non può accadere sulla GPU. :) Inoltre, non intendo, personalmente, sovraperformare le funzioni della libreria (quindi perché pongo la domanda qui) ma sono sicuro che ci sia qualcuno sullo stackoverflow che può superare le libs :) Inoltre, gli scrittori di librerie sono in genere limitati per requisiti di portabilità - come ho affermato, mi interessa solo la piattaforma x86, quindi forse sono possibili ulteriori x86 ottimizzazioni specifiche. – horseyguy

+0

+1 poiché è un buon primo consiglio da dare - anche se non si applica nel caso del balaustro. – peterchen

+1

Non sono sicuro che sia un buon consiglio. Una tipica macchina moderna ha circa la stessa larghezza di banda di memoria per CPU e GPU. Ad esempio, i molti portatili più diffusi utilizzano la grafica Intel HD, che utilizza la stessa RAM della CPU. La CPU può già saturare il bus di memoria. Per memcpy, mi aspetterei prestazioni simili sulla CPU o GPU. –

3

Se specifico per i processori Intel, è possibile trarre vantaggio da IPP. Se sai che verrà eseguito con una GPU Nvidia, potresti usare CUDA - in entrambi i casi potrebbe essere meglio sembrare più ampio dell'ottimizzazione di memcpy() - offrono opportunità per migliorare l'algoritmo a un livello più alto. Entrambi sono tuttavia dipendenti da hardware specifico.

6

A qualsiasi livello di ottimizzazione di -O1 o superiore, GCC utilizzerà le definizioni incorporati per funzioni come memcpy - con il parametro di -march destra (-march=pentium4 per l'insieme delle caratteristiche si parla) che dovrebbe generare codice inline specifico per la graziosa architettura ottimale.

Vorrei confrontarlo e vedere cosa viene fuori.

6

Il codice SSE pubblicato da hapalibashi è la strada da percorrere.

Se avete bisogno di prestazioni ancora maggiori e non evitate la lunga e tortuosa strada della scrittura di un dispositivo-driver: tutte le piattaforme importanti oggi hanno un controller DMA che è in grado di fare un lavoro di copia più veloce e in potrebbe fare il codice parallelo alla CPU.

Ciò significa scrivere un driver. Nessun sistema operativo grande di cui sono a conoscenza espone questa funzionalità al lato utente a causa dei rischi per la sicurezza.

Tuttavia, potrebbe valerne la pena (se è necessaria la prestazione) poiché nessun codice sulla terra potrebbe superare la prestazione di un componente hardware progettato per svolgere tale lavoro.

+1

Ho appena postato una risposta che parla della larghezza di banda della RAM. Se quello che dico è vero, allora non credo che il motore DMA possa ottenere molto più di quello che può ottenere la CPU. Ho perso qualcosa? –

5

Questa domanda ha ora quattro anni e sono un po 'sorpresa che nessuno abbia menzionato la larghezza di banda della memoria. CPU-Z segnala che la mia macchina ha una RAM PC3-10700. Che la RAM abbia un'ampiezza di banda di picco (ovvero velocità di trasferimento, velocità effettiva, ecc.) Di 10700 MByte/sec. La CPU nella mia macchina è una CPU i5-2430M, con una frequenza di picco del turbo di 3 GHz.

In teoria, con una CPU veloce e infinitamente la mia RAM, memcpy potrebbe andare a 5300 MByte/sec, vale a dire la metà di 10700 perché memcpy deve leggere e poi scrivere alla RAM. (modifica: come ho sottolineato, questa è un'approssimazione semplicistica).

D'altra parte, immagina di avere una RAM infinitamente veloce e una CPU realistica, cosa potremmo ottenere? Usiamo la mia CPU 3 GHz come esempio. Se potesse eseguire una lettura a 32 bit e una scrittura a 32 bit per ogni ciclo, potrebbe trasferire 3e9 * 4 = 12000 MByte/sec. Questo sembra facilmente alla portata di una CPU moderna. Già, possiamo vedere che il codice in esecuzione sulla CPU non è proprio il collo di bottiglia. Questo è uno dei motivi per cui le macchine moderne hanno cache di dati.

Siamo in grado di misurare ciò che la CPU può realmente fare eseguendo il benchmarking di memcpy quando sappiamo che i dati sono memorizzati nella cache. Fare questo con precisione è poco pratico. Ho creato una semplice app che ha scritto numeri casuali in un array, li abbiamo memcpy in un altro array, quindi ha eseguito il checksum dei dati copiati. Ho fatto un passo attraverso il codice nel debugger per assicurarmi che il compilatore intelligente non avesse rimosso la copia. La modifica della dimensione dell'array altera le prestazioni della cache: piccoli array si adattano alla cache, quelli grandi meno. Ho i seguenti risultati:

  • 40 KByte array: 16000 MB/s
  • 400 KByte array: 11000 MB/s
  • 4000 matrici KByte: 3100 MByte/sec

Ovviamente, la mia CPU può leggere e scrivere più di 32 bit per ciclo, dal momento che 16000 è più del 12000 che ho calcolato teoricamente sopra. Ciò significa che la CPU è ancora meno un collo di bottiglia di quanto pensassi. Ho usato Visual Studio 2005 e, passando all'applicazione standard di memcpy, vedo che usa le istruzioni movqda sulla mia macchina. Immagino che questo possa leggere e scrivere 64 bit per ciclo.

Il bel codice pubblicato hamagibashi raggiunge 4200 MByte/sec sulla mia macchina, circa il 40% più veloce rispetto all'implementazione VS 2005. Immagino sia più veloce perché usa l'istruzione prefetch per migliorare le prestazioni della cache.

In sintesi, il codice in esecuzione sulla CPU non è il collo di bottiglia e l'ottimizzazione del codice comporterà solo piccoli miglioramenti.

+0

Il tuo modo di pensare è buono. Comunque ti manca pensare ai numeri di marketing della RAM, sono tutte figure quadrate, che non corrisponde alla velocità di 1 canale. Ed è anche la velocità prima del bus, ci sono anche le spese generali di gestione nel modello numa che hanno i core i7/opteron. –

Problemi correlati