2009-07-30 19 views
14

Esistono alternative più veloci a memcpy() in C++?memcpy ottimizzato

+23

Se ci fosse un modo più veloce, perché non lo userebbero nell'implementazione di 'memcpy'? –

+0

Che dire di SSE? –

+0

@MehrdadAfshari: La funzione 'memcpy' può essere invocata con puntatori di allineamento arbitrario, a cose di tipo PODS arbitrario, e può arbitrariamente aliasare qualsiasi oggetto PODS il cui indirizzo è stato esposto a codice esterno. Dato 'struct fnord a, b; void * volatile p = & a, * volatile q = & b; 'Mi aspetterei che * * ((struct fnord *) p) = * ((struct fnord *) q);' funzioni molto meglio di 'memcpy (p, q , sizeof (struct fnord)); dal momento che nel primo caso un compilatore può legittimamente assumere che p e q saranno allineati per una 'struct fnord' e non alias nient'altro, ma nel secondo caso non può. – supercat

risposta

17

Improbabile. Il tuo compilatore/libreria standard avrà probabilmente un'implementazione molto efficiente e su misura di memcpy. E memcpy è fondamentalmente l'apice più basso che c'è per copiare una parte della memoria in un'altra.

Se si desiderano ulteriori accelerazioni, trovare un modo per non aver bisogno di copiare la memoria.

+0

in realtà, c'è almeno un'alternativa che sarà più veloce in * alcuni * casi almeno e non dovrebbe mai essere più lenta. Vedi la mia risposta. :) – jalf

+0

-1: è risaputo che le funzioni incorporate di GCC fanno schifo (vedi benchmark di Agner Fog). Bene, forse è stato finalmente risolto, ma illustra il punto che le librerie * non * sono necessariamente ottimizzate. –

+0

@Bastien: potresti fornire un puntatore ai benchmark Agner Fog? Vedo che sul suo sito ci sono molte informazioni sull'ottimizzazione, ma non sono riuscito a trovare benchmark chiari (ad eccezione di una tabella che confrontava alcune routine di memcpy() e strlen() e per quanto posso dire del supporto intrinseco perché la routine era disattivata). –

7

Questa risposta per una domanda molto simile (circa memset()) si applica anche qui.

Fondamentalmente dice che compilatori generano codice molto ottimale per memcpy()/memset() - e codice diverso a seconda della natura degli oggetti (dimensioni, allineamento, ecc).

E ricorda, solo memcpy() POD in C++.

1

A seconda di cosa si sta tentando di fare ... se si tratta di una memcpy abbastanza grande, e si sta scrivendo solo in copia scarsamente, un mmap con MMAP_PRIVATE per creare una mappatura copy-on-write potrebbe essere Più veloce.

+0

Tuttavia, ciò richiede di scriverlo su un file in primo luogo ... – bdonlan

+0

E la copia su materiale di scrittura funzionerà solo se lo spazio degli indirizzi è in un processo diverso (è tornato per dirlo) In realtà non penso che tu debba scriverlo su un file se usi il flag MAP_ANONYMOUS. – smcameron

+3

no, la mappatura della memoria può essere utilizzata anche tra due posizioni di memoria – jalf

1

A seconda della piattaforma, potrebbero esserci casi d'uso specifici, come se si sapesse che l'origine e la destinazione sono allineate a una linea della cache e la dimensione è un multiplo intero della dimensione della linea della cache. In generale, la maggior parte dei compilatori produrrà comunque un codice abbastanza ottimale per memcpy.

19

In primo luogo, un consiglio. Supponi che le persone che hanno scritto la tua libreria standard non siano stupide. Se ci fosse un modo più veloce per implementare una memcpy generale, l'avrebbero fatto.

In secondo luogo, sì, ci sono alternative migliori.

  • In C++, utilizzare la funzione std::copy. Fa la stessa cosa, ma è 1) più sicuro, e 2) potenzialmente più veloce in alcuni casi. È un modello, il che significa che può essere specializzato per tipi specifici, rendendolo potenzialmente più veloce della memoria generale C.
  • Oppure, puoi utilizzare le tue conoscenze superiori a la tua situazione specifica. Gli esecutori di memcpy hanno dovuto scriverlo in modo che si comportasse bene nel caso ogni. Se si dispone di informazioni specifiche sulla situazione in cui è necessario, è possibile scrivere una versione più veloce. Ad esempio, quanta memoria hai bisogno di copiare? Come è allineato? Questo potrebbe consentire di scrivere una memcpy più efficiente per questo caso specifico. Ma non sarà buono nella maggior parte degli altri casi (se funzionerà del tutto)
+7

È improbabile che il compilatore in realtà chiami una funzione memcpy. So che in gcc non funziona, ma in realtà sostituisce memcpy con una singola istruzione su i386. –

+1

@PaulBiggar: per i tipi POD, la copia std :: copy di GCC chiamerà 'memmove'. Se fornisci suggerimenti di aliasing con '__restrict', chiamerà' memcpy'. –

1

Non sono sicuro che l'utilizzo della memcpy predefinita sia sempre l'opzione migliore. La maggior parte delle implementazioni di memcpy che ho guardato tendono a provare ad allineare i dati all'inizio, e quindi a fare copie allineate. Se i dati sono già allineati, o sono piuttosto piccoli, allora questo è uno spreco di tempo.

A volte è utile disporre di una copia di parola specializzata, di una copia di mezza parola, di una copia di byte memorizzata, purché non abbia un effetto negativo sulle cache.

Inoltre, si consiglia un controllo più preciso sull'algoritmo di allocazione effettivo.Nel settore dei giochi è eccezionalmente comune per le persone scrivere le proprie routine di allocazione della memoria, indipendentemente da quanto sia stato speso lo sforzo dagli sviluppatori di toolchain, in primo luogo, nello svilupparlo. I giochi che ho visto tendono quasi sempre a usare Doug Lea's Malloc.

In generale, però, si sprecherà tempo cercando di ottimizzare memcpy poiché ci saranno senza dubbio molti più codici di codice nell'applicazione per accelerare.

7

Esperto di ottimizzazione Agner Fog ha pubblicato funzioni di memoria ottimizzate: http://agner.org/optimize/#asmlib. È sotto GPL però.

Qualche tempo fa Agner ha detto che queste funzioni dovrebbero sostituire i builder GCC perché sono molto più veloci. Non so se è stato fatto da allora.

2

Per trovare o scrivere una routine di copia di memoria veloce, dobbiamo capire come funzionano i processori.

Processori da quando Intel Pentium Pro esegue "Esecuzione fuori servizio". Possono eseguire molte istruzioni in parallelo se le istruzioni non hanno dipendenze. Ma questo è solo il caso in cui le istruzioni operano solo con i registri. Se funzionano con la memoria, vengono utilizzate unità CPU aggiuntive, chiamate "unità di carico" (per leggere i dati dalla memoria) e "unità di memoria" (per scrivere i dati nella memoria). La maggior parte delle CPU ha due unità di carico e un'unità di negozio, cioè possono eseguire in parallelo due istruzioni che leggono dalla memoria e un'istruzione che scrive in memoria (anche in questo caso, se non si influenzano a vicenda). La dimensione di queste unità è solitamente uguale alla dimensione massima del registro - se la CPU ha registri XMM (SSE) - è 16 byte, se ha registri YMM (AVX) - è 32 byte e così via. Tutte le istruzioni che leggono o scrivono memoria vengono tradotte in micro-operazioni (micro-op) che vanno al pool comune di micro-op e aspettano lì che il carico e le unità di archiviazione siano in grado di servirle. Un singolo carico o unità di archiviazione può servire solo una micro-op alla volta, indipendentemente dalla dimensione dei dati che deve essere caricata o archiviata, sia essa 1 byte o 32 byte.

Quindi, la copia di memoria più veloce sarebbe spostata da e verso registri con dimensione massima. Per i processori AVX abilitati, modo più veloce per copiare la memoria potrebbe essere quella di ripetere la seguente sequenza, loop-srotolò:

vmovdqa  ymm0,ymmword ptr [rcx] 
vmovdqa  ymm1,ymmword ptr [rcx+20h] 
vmovdqa  ymmword ptr [rdx],ymm0 
vmovdqa  ymmword ptr [rdx+20h],ymm1 

Il codice di Google pubblicato in precedenza da hplbsh non è molto buona, perché usano tutti gli 8 registri XMM a conservare i dati prima che inizino a scriverli, mentre non è necessario, dal momento che abbiamo solo due unità di carico e un'unità di vendita. Quindi solo due registri danno i migliori risultati. L'utilizzo di molti registri non migliora in alcun modo le prestazioni.

Una routine di copia di memoria può anche utilizzare alcune tecniche "avanzate" come "prefetch" per indicare al processore di caricare la memoria in cache in anticipo e "scritture non temporali" (se si copiano blocchi di memoria molto grandi e don " i dati dal buffer di output devono essere letti immediatamente), allineati vs scritture non allineate, ecc.

Processori moderni, rilasciati dal 2013, se hanno il bit ERMS nella CPUID, hanno il cosiddetto "enhanced rep movsb ", Quindi per la copia di memoria di grandi dimensioni, è possibile utilizzare" rep movsb "- la copia sarà molto veloce, persino più veloce rispetto ai registri ymm, e funzionerà correttamente con la cache. Tuttavia, i costi di avvio di questa istruzione sono molto alti - circa 35 cicli, quindi si paga solo su blocchi di memoria di grandi dimensioni.

Spero che ora sia più facile per voi scegliere o scrivere la migliore routine di copia di memoria necessaria per il vostro caso.

È anche possibile mantenere la memcpy/memmove standard, ma ottenere il proprio speciale largememppy() per le proprie esigenze.