2015-01-14 29 views
5

Sto eseguendo un assemblaggio x64 con Visual C++ 2010 e masm (convenzione di chiamata "chiamata rapida").Come compilare un registro a 64 bit con valori di byte duplicati

Quindi diciamo che ho una funzione in C++:

extern "C" void fillArray(unsigned char* byteArray, unsigned char value); 

Il puntatore a vettore sarà in RCX e il valore char sarà in DL

Come posso riempire RAX con valori utilizzando DL tale che se io dovessi mov qword ptr [RCX], RAX e stampare byteArray, tutti i valori sarebbero uguali a 'valore char'?

Si prega di notare che non sto cercando di fare out-code il mio compilatore, sto solo imparando.

+0

Se si desidera imparare un po 'di MMX/SSE ci sono tali istruzioni. Ma in questo caso sarà probabilmente più lento perché è solo per un valore. SSE funziona molto meglio se si eseguono calcoli su più valori contemporaneamente. –

risposta

6

Perché hai chiamato la procedura 'fillArray', ho presupposto che ti piacesse riempire un intero blocco di memoria con un valore di byte. Quindi ho fatto un confronto su approcci diversi. È un codice masm a 32 bit, ma i risultati dovrebbero essere simili nella modalità 64 bit. Ogni approccio è testato con entrambi i buffer allineati e non allineati. Ecco i risultati:

Simple REP STOSB - aligned....: 192 
Simple REP STOSB - not aligned: 192 
Simple REP STOSD - aligned....: 191 
Simple REP STOSD - not aligned: 222 
Simple while loop - aligned....: 267 
Simple while loop - not aligned: 261 
Simple while loop with different addressing - aligned....: 271 
Simple while loop with different addressing - not aligned: 262 
Loop with 16-byte SSE write - aligned....: 192 
Loop with 16-byte SSE write - not aligned: 205 
Loop with 16-byte SSE write non-temporal hint - aligned....: 126 (EDIT) 

La variante più ingenuo utilizzando il seguente codice sembra per eseguire meglio in entrambi gli scenari e ha la dimensione del codice più piccolo così:

cld 
mov al, 44h ; byte value 
mov edi, lpDst 
mov ecx, 256000*4 ; buf size 
rep stosb 

EDIT: Non è il più veloce per dati allineati. Aggiunta la versione MOVNTDQ che offre le prestazioni migliori, vedi sotto.

Per ragioni di completezza, qui ci sono estratti dalle altre routine - il valore si presume essere ampliato in EAX prima:

Rep Stosd:

mov edi, lpDst 
mov ecx, 256000 
rep stosd 

semplice, mentre:

mov edi, lpDst 
mov ecx, 256000 
.while ecx>0 
    mov [edi],eax 
    add edi,4 
    dec ecx 
.endw 

Diversi semplici mentre:

mov edi, lpDst 
xor ecx, ecx 
.while ecx<256000 
    mov [edi+ecx*4],eax 
    inc ecx 
.endw 

SSE (entrambi):

movd xmm0,eax 
punpckldq xmm0,xmm0 ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH 
punpcklqdq xmm0,xmm0 ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH 
mov ecx, 256000/4 ; 16 byte 
mov edi, lpDst 
.while ecx>0 
    movdqa xmmword ptr [edi],xmm0 ; movdqu for unaligned 
    add edi,16 
    dec ecx 
.endw 

SSE (NT, allineato, EDIT):

movd xmm0,eax 
punpckldq xmm0,xmm0 ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH 
punpcklqdq xmm0,xmm0 ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH 
mov ecx, 256000/4 ; 16 byte 
mov edi, lpDst 
.while ecx>0 
    movntdq xmmword ptr [edi],xmm0 
    add edi,16 
    dec ecx 
.endw 

ho caricato l'intero codice qui http://pastie.org/9831404 --- pacchetto MASM dalla gabbia è necessaria per l'assemblaggio .

+0

Per quanto riguarda la CPU su cui hai eseguito questi benchmark? Sono interessato a sapere se SB/IB/Haswell vedrebbe benefici simili dall'utilizzo di negozi non temporali? –

+0

Ho usato una CPU AMD x4 640 con DDR3-RAM con clock a 1333. – zx485

+0

Grazie - proverò a eseguire il codice su un Haswell e vedere se fornisce risultati simili. –

9

È possibile moltiplicare per 0x0101010101010101 per copiare il byte più basso in tutti gli altri byte (supponendo che gli altri sono stati tutti a zero per cominciare), è un po 'fastidioso perché non c'è imul r64, r64, imm64 ma è possibile potrebbe fare questo:

mov rax, 0x0101010101010101 
mul rdx 

Su alcuni processori, è leggermente più veloce utilizzare imul rax, rdx anziché mul rdx.

Se rdx non è della forma richiesta (in altre parole, se ha alcuni bit extra set), è sufficiente aggiungere un
movzx rdx, dl davanti.

Se non ti piace la dimensione del codice (mov r64, imm64 è già 10 byte di per sé), basta mantenere quella costante nel segmento di dati.

+0

Quella parte della moltiplicazione per costante è esattamente il tipo di cosa che stavo cercando. – Dziugas

+1

Per i futuri lettori del q/a: trasmettere un byte in un registro SSE potrebbe essere una scelta migliore per impostare un memset (aka fillArray). Usare le istruzioni di interi per trasmetterle prima a un registro 32b (ad es. Con questo trucco imul'), e poi fare 'movd' potrebbe avere senso, oppure usare pshufb con una maschera di controllo tutto-zero (che puoi generare in modo efficiente con pxor alias '_mm_setzero()'). –

2

modo ingenuo

xor rbx, rbx 
mov bl, dl 
mov bh, dl 
mov rax, rbx 
shl rbx, 16 
or rbx, rax 
mov rax, rbx 
shl rax, 32 
or rax, rbx 

quindi potrebbe essere molto più lento di modo di Harold

Si può anche guardare in uscita il montaggio del compilatore per il seguente codice

int64_t s; 
s = (s << 8) | s; 
s = (s << 16) | s; 
s = (s << 32) | s; 

gcc 4.9.0 genera the following output con il risultato in rsi

mov rsi, rax 
sal rsi, 8 
or rsi, rax 
mov rax, rsi 
sal rax, 16 
or rsi, rax 
mov rax, rsi 
sal rax, 32 
or rsi, rax