Domanda

Abbiamo macchine Core2 (Dell T5400) con XP64.

Osserviamo che quando si eseguono processi a 32 bit, le prestazioni di Memcpy sono nell'ordine di 1,2 GByte/s;Tuttavia, Memcpy in un processo a 64 bit raggiunge circa 2,2 GByte/s (o 2,4 GByte/s con la memcpy del compilatore Intel CRT).Mentre la reazione iniziale potrebbe essere quella di spiegarlo semplicemente a causa dei registri più ampi disponibili in codice a 64 bit, osserviamo che il nostro codice di assemblaggio SSE simile a memcpy (che dovrebbe utilizzare negozi di carico largo a 128 bit indipendentemente da 32 /64 bitness del processo) mostra limiti superiori simili sulla larghezza di banda della copia che raggiunge.

La mia domanda è: a cosa serve questa differenza?I processi a 32 bit devono saltare attraverso alcuni cerchi Wow64 extra per arrivare al RAM?È qualcosa a che fare con TLB o prefetcher o ...Che cosa ?

Grazie per qualsiasi intuizione.

Anche cresciuto Forum Intel.

È stato utile?

Soluzione

Naturalmente, devi davvero guardare le istruzioni della macchina reale che vengono eseguite all'interno del ciclo più interno di memcpy, entrando nel codice macchina con un debugger. Qualsiasi altra cosa è solo una speculazione.

La mia domanda è che probabilmente non ha nulla a che fare con i 32 bit contro i 64 bit in sé; la mia ipotesi è che la routine di libreria più veloce sia stata scritta utilizzando gli archivi non temporali SSE.

Se il circuito interno contiene una variazione delle istruzioni convenzionali per l'archiviazione del carico, quindi la memoria di destinazione deve essere letta nella cache della macchina, modificata e riscritta. Dato che quella lettura non è assolutamente necessaria - i bit letti vengono sovrascritti immediatamente - è possibile risparmiare metà della larghezza di banda della memoria usando & Quot; non temporali & Quot; scrivere istruzioni che bypassano le cache. In questo modo, la memoria di destinazione viene appena scritta facendo un viaggio di sola andata nella memoria anziché un viaggio di andata e ritorno.

Non conosco la libreria CRT del compilatore Intel, quindi questa è solo una supposizione. Non c'è un motivo particolare per cui il libCRT a 32 bit non può fare la stessa cosa, ma l'accelerazione che citi è nel campo di gioco di quello che mi aspetterei semplicemente convertendo le istruzioni di movdqa in movnt ...

Poiché memcpy non sta eseguendo calcoli, è sempre vincolato dalla velocità con cui è possibile leggere e scrivere memoria.

Altri suggerimenti

Penso che quanto segue possa spiegarlo:

Per copiare i dati dalla memoria a un registro e di nuovo in memoria, lo fai

mov eax, [address]
mov [address2], eax

Questo sposta 32 bit (4 byte) dall'indirizzo all'indirizzo2.Lo stesso vale con 64 bit in modalità 64 bit

mov rax, [address]
mov [address2], rax

Questo sposta 64 bit, 2 byte, dall'indirizzo all'indirizzo2."mov" stesso, indipendentemente dal fatto che sia a 64 o 32 bit, ha una latenza di 0,5 e un throughput di 0,5 secondo le specifiche Intel.La latenza è il numero di cicli di clock necessari all'istruzione per viaggiare attraverso la pipeline, mentre il throughput è il tempo che la CPU deve attendere prima di accettare nuovamente la stessa istruzione.Come puoi vedere, può eseguire due movimenti per ciclo di clock, tuttavia, deve attendere mezzo ciclo di clock tra due movimenti, quindi può effettivamente eseguire solo un movimento per ciclo di clock (o sbaglio qui e interpreto male i termini?Vedere PDF qui per dettagli).

Naturalmente a mov reg, mem può essere più lungo di 0,5 cicli, a seconda se i dati si trovano nella cache di 1° o 2° livello o non sono affatto nella cache e devono essere estratti dalla memoria.Tuttavia, il tempo di latenza di cui sopra ignora questo fatto (come afferma il PDF che ho collegato sopra), presuppone che tutti i dati necessari per il movimento siano già presenti (altrimenti la latenza aumenterà in base al tempo necessario per recuperare i dati ovunque si trovino) in questo momento - potrebbero trattarsi di diversi cicli di clock ed è completamente indipendente dal comando eseguito, afferma il PDF a pagina 482/C-30).

Ciò che è interessante è che il filmato sia a 32 o 64 bit non ha alcun ruolo.Ciò significa che, a meno che la larghezza di banda della memoria non diventi un fattore limitante, i mov a 64 bit sono altrettanto veloci dei mov a 32 bit e poiché è necessaria solo la metà dei mov per spostare la stessa quantità di dati da A a B quando si utilizza 64 bit, il throughput può (in teoria) essere il doppio (il fatto che non lo sia probabilmente perché la memoria non è illimitata).

Ok, ora pensi che quando usi i registri SSE più grandi, dovresti ottenere un throughput più veloce, giusto?Per quanto ne so, i registri xmm non sono 256, ma larghi 128 bit, a proposito (riferimento a Wikipedia).Tuttavia, hai considerato la latenza e il throughput?I dati che desideri spostare sono allineati a 128 bit oppure no.A seconda di ciò, puoi spostarlo utilizzando

movdqa xmm1, [address]
movdqa [address2], xmm1

o se non allineato

movdqu xmm1, [address]
movdqu [address2], xmm1

Bene, movdqa/movdqu ha una latenza di 1 e un throughput di 1.Quindi le istruzioni impiegano il doppio del tempo per essere eseguite e il tempo di attesa dopo le istruzioni è doppio rispetto a un normale movimento.

E un'altra cosa di cui non abbiamo nemmeno preso in considerazione è il fatto che la CPU in realtà suddivide le istruzioni in micro-operazioni e può eseguirle in parallelo.Ora inizia a diventare davvero complicato...anche troppo complicato per me.

Ad ogni modo, so per esperienza che il caricamento dei dati da/verso i registri xmm è molto più lento del caricamento dei dati da/verso i registri normali, quindi la tua idea di accelerare il trasferimento utilizzando i registri xmm è stata condannata fin dal primo secondo.In realtà sono sorpreso che alla fine il memmove SSE non sia molto più lento di quello normale.

Finalmente sono arrivato al fondo di questo (e la risposta di Die in Sente è stata sulla linea giusta, grazie)

Nel seguito, dst e src sono 512 MByte std :: vector. Sto usando il compilatore Intel 10.1.029 e CRT.

Su 64 bit entrambi

  

memcpy(&dst[0],&src[0],dst.size())

e

  

memcpy(&dst[0],&src[0],N)

dove N è stato precedentemente dichiarato const size_t N=512*(1<<20); chiama

  

__intel_fast_memcpy

la maggior parte dei quali è costituita da:

  000000014004ED80  lea         rcx,[rcx+40h] 
  000000014004ED84  lea         rdx,[rdx+40h] 
  000000014004ED88  lea         r8,[r8-40h] 
  000000014004ED8C  prefetchnta [rdx+180h] 
  000000014004ED93  movdqu      xmm0,xmmword ptr [rdx-40h] 
  000000014004ED98  movdqu      xmm1,xmmword ptr [rdx-30h] 
  000000014004ED9D  cmp         r8,40h 
  000000014004EDA1  movntdq     xmmword ptr [rcx-40h],xmm0 
  000000014004EDA6  movntdq     xmmword ptr [rcx-30h],xmm1 
  000000014004EDAB  movdqu      xmm2,xmmword ptr [rdx-20h] 
  000000014004EDB0  movdqu      xmm3,xmmword ptr [rdx-10h] 
  000000014004EDB5  movntdq     xmmword ptr [rcx-20h],xmm2 
  000000014004EDBA  movntdq     xmmword ptr [rcx-10h],xmm3 
  000000014004EDBF  jge         000000014004ED80 

e funziona a ~ 2200 MByte / s.

Ma a 32 bit

  

_mm_stream_ps

chiamate

  

dst.size()

la maggior parte delle quali è costituita da

  004447A0  sub         ecx,80h 
  004447A6  movdqa      xmm0,xmmword ptr [esi] 
  004447AA  movdqa      xmm1,xmmword ptr [esi+10h] 
  004447AF  movdqa      xmmword ptr [edx],xmm0 
  004447B3  movdqa      xmmword ptr [edx+10h],xmm1 
  004447B8  movdqa      xmm2,xmmword ptr [esi+20h] 
  004447BD  movdqa      xmm3,xmmword ptr [esi+30h] 
  004447C2  movdqa      xmmword ptr [edx+20h],xmm2 
  004447C7  movdqa      xmmword ptr [edx+30h],xmm3 
  004447CC  movdqa      xmm4,xmmword ptr [esi+40h] 
  004447D1  movdqa      xmm5,xmmword ptr [esi+50h] 
  004447D6  movdqa      xmmword ptr [edx+40h],xmm4 
  004447DB  movdqa      xmmword ptr [edx+50h],xmm5 
  004447E0  movdqa      xmm6,xmmword ptr [esi+60h] 
  004447E5  movdqa      xmm7,xmmword ptr [esi+70h] 
  004447EA  add         esi,80h 
  004447F0  movdqa      xmmword ptr [edx+60h],xmm6 
  004447F5  movdqa      xmmword ptr [edx+70h],xmm7 
  004447FA  add         edx,80h 
  00444800  cmp         ecx,80h 
  00444806  jge         004447A0

e funziona solo a ~ 1350 MByte / s.

TUTTAVIA

memcpy(&dst[0],&src[0],N)

dove N è stato precedentemente dichiarato movnt viene compilato (su 32 bit) in una chiamata diretta a un

__intel_VEC_memcpy

la maggior parte delle quali è costituita da

  0043FF40  movdqa      xmm0,xmmword ptr [esi] 
  0043FF44  movdqa      xmm1,xmmword ptr [esi+10h] 
  0043FF49  movdqa      xmm2,xmmword ptr [esi+20h] 
  0043FF4E  movdqa      xmm3,xmmword ptr [esi+30h] 
  0043FF53  movntdq     xmmword ptr [edi],xmm0 
  0043FF57  movntdq     xmmword ptr [edi+10h],xmm1 
  0043FF5C  movntdq     xmmword ptr [edi+20h],xmm2 
  0043FF61  movntdq     xmmword ptr [edi+30h],xmm3 
  0043FF66  movdqa      xmm4,xmmword ptr [esi+40h] 
  0043FF6B  movdqa      xmm5,xmmword ptr [esi+50h] 
  0043FF70  movdqa      xmm6,xmmword ptr [esi+60h] 
  0043FF75  movdqa      xmm7,xmmword ptr [esi+70h] 
  0043FF7A  movntdq     xmmword ptr [edi+40h],xmm4 
  0043FF7F  movntdq     xmmword ptr [edi+50h],xmm5 
  0043FF84  movntdq     xmmword ptr [edi+60h],xmm6 
  0043FF89  movntdq     xmmword ptr [edi+70h],xmm7 
  0043FF8E  lea         esi,[esi+80h] 
  0043FF94  lea         edi,[edi+80h] 
  0043FF9A  dec         ecx  
  0043FF9B  jne         ___intel_VEC_memcpy+244h (43FF40h) 

e funziona a ~ 2100MByte / s (e dimostrare che 32 bit non è in qualche modo limitato dalla larghezza di banda).

Ritiro la mia affermazione che il mio codice SSE simile a memcpy soffre di a simile ~ 1300 MByte / limite nelle build a 32 bit; Ora non ho problemi ottenere > 2 GByte / s su 32 o 64 bit; il trucco (come suggeriscono i risultati precedenti) consiste nell'utilizzare negozi non temporali (" streaming ") (ad esempio CPUID intrinseco).

Sembra un po 'strano che il 32 bit " <=> " memcpy alla fine non lo fa chiama il più veloce " <=> " versione (se passi a memcpy c'è di più incredibile quantità di <=> logica euristica e di controllo, ad es. confronto dei numeri di byte da copiare con dimensioni della cache ecc. prima che vadano ovunque vicino a dati reali) ma almeno ora capisco il comportamento osservato (ed è non correlato a SysWow64 o H / W).

La mia ipotesi immediata è che i processi a 64 bit utilizzano la dimensione di memoria nativa a 64 bit del processore, il che ottimizza l'uso del bus di memoria.

Grazie per il feedback positivo!credo che posso in parte spiegare cosa sta succedendo qui.

Usare gli archivi non temporali per memcpy è sicuramente il digiuno Se stai solo cronometrando la chiamata memcpy.

D'altra parte, se stai confrontando un'applicazione, gli archivi movdqa hanno il vantaggio di lasciare la memoria di destinazione nella cache.O almeno la parte che sta nella cache.

Pertanto, se stai progettando una libreria runtime e se puoi presumere che l'applicazione che ha chiamato memcpy utilizzerà il buffer di destinazione immediatamente dopo la chiamata memcpy, allora ti consigliamo di fornire la versione movdqa.Ciò ottimizza efficacemente il viaggio dalla memoria alla CPU che seguirebbe la versione movntdq e tutte le istruzioni successive alla chiamata verranno eseguite più velocemente.

D'altra parte, se il buffer di destinazione è grande rispetto alla cache del processore, l'ottimizzazione non funziona e la versione movntdq fornirebbe benchmark delle applicazioni più rapidi.

Quindi l'idea memcpy avrebbe più versioni sotto il cofano.Quando il buffer di destinazione è piccolo rispetto alla cache del processore, utilizza movdqa, altrimenti, se il buffer di destinazione è grande rispetto alla cache del processore, utilizza movntdq.Sembra che questo sia ciò che sta accadendo nella libreria a 32 bit.

Naturalmente, nulla di tutto ciò ha a che fare con le differenze tra 32 bit e 64 bit.

La mia congettura è che la libreria a 64 bit non sia così matura.Gli sviluppatori non sono ancora riusciti a fornire entrambe le routine in quella versione della libreria.

Non ho un riferimento davanti a me, quindi non sono assolutamente positivo sui tempi / istruzioni, ma posso ancora dare la teoria. Se stai eseguendo uno spostamento della memoria in modalità a 32 bit, eseguirai qualcosa di simile a un & Quot; rep movsd & Quot; che sposta un singolo valore di 32 bit ad ogni ciclo di clock. In modalità 64-bit, puoi fare un & Quot; rep movsq & Quot; che esegue un singolo spostamento di 64 bit ad ogni ciclo di clock. Quell'istruzione non è disponibile per il codice a 32 bit, quindi faresti 2 x rep movsd (a 1 ciclo un pezzo) per metà della velocità di esecuzione.

MOLTO semplificato, ignorando tutti i problemi di larghezza di banda / allineamento della memoria, ecc., ma è qui che inizia tutto ...

Ecco un esempio di una routine memcpy orientata specificamente per l'architettura a 64 bit.

void uint8copy(void *dest, void *src, size_t n){
    uint64_t * ss = (uint64_t)src;
    uint64_t * dd = (uint64_t)dest;
    n = n * sizeof(uint8_t)/sizeof(uint64_t); 

    while(n--)
        *dd++ = *ss++;
}//end uint8copy()

L'articolo completo è qui: http://www.godlikemouse.com/2008/03/04/ ottimizzando-memcpy-routine /

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