2015-08-07 16 views
8

Ho un semplice codice a 32 bit che calcola il prodotto di un array di interi a 32 bit. Il ciclo interno si presenta così:Perché il giro di andata e ritorno della memoria è più veloce del non effettuare il viaggio di andata e ritorno?

@@loop: 
mov esi,[ebx] 
mov [esp],esi 
imul eax,[esp] 
add ebx, 4 
dec edx 
jnz @@loop 

Quello che sto cercando di capire è il motivo per cui il codice di cui sopra è del 6% più veloce di queste due versioni del codice, che non svolge la memoria di andata e ritorno ridondante:

@@loop: 
mov esi,[ebx] 
imul eax,esi 
add ebx, 4 
dec edx 
jnz @@loop 

e

@@loop: 
imul eax,[ebx] 
add ebx, 4 
dec edx 
jnz @@loop 

Gli ultimi due pezzi di codice eseguire praticamente nello stesso tempo, e come accennato entrambi sono 6% più lento del primo pezzo (165ms vs 155ms, 200 milioni di elementi).

Ho provato ad allineare manualmente il target di salto a un limite di 16 byte, ma non fa alcuna differenza.

L'ho eseguito su un Intel i7 4770k, Windows 10 x64.

Nota: so che il codice potrebbe essere migliorato eseguendo tutti i tipi di ottimizzazioni, tuttavia mi interessa solo la differenza di prestazioni tra le parti di codice sopra riportate.

+0

Non posso darti riferimenti (perché probabilmente non esistono, dal momento che rivelerebbero segreti commerciali), ma probabilmente stai vedendo un artefatto dello straordinario sforzo che Intel mette nelle prestazioni della cache L1. – Gene

+2

Si verifica ancora quando si inserisce un carico fittizio 'mov ecx, [ebx]' nella seconda versione? – harold

+0

Come vanno le prestazioni nel caso memorizzato nella cache? Il primo ciclo dovrebbe uscire dal buffer del ciclo ad uno per 2 cicli (dal momento che sono 5 domini con dominio fuso sulla CPU Haswell). Gli altri due possono rilasciare un ciclo per iterazione. Tuttavia, la catena di dipendenza "imul" trasportata da loop dovrebbe limitarli tutti a 3 cicli per iterazione.Il primo non ha lo store e non ricarica nella catena di dipendenze del percorso critico, e Haswell può eseguire 2x load + 1x store ogni ciclo. (Pre-Haswell non aveva un AGU dedicato al negozio). Non riesco a capire perché è più veloce, ma ha senso che non sia più lento. –

risposta

1

ho il sospetto, ma non posso essere sicuro che si sta impedendo una stalla su una dipendenza di dati:

Il codice è simile al seguente:

@@loop: 
    mov esi,[ebx] # (1)Load the memory location to esi reg 
    (mov [esp],esi) # (1)optionally store the location on the stack  
    imul eax,[esp] # (3) Perform the multiplication 
    add ebx, 4  # (1) Add 4 
    dec edx   # (1)decrement counter 
    jnz @@loop  # (0**) loop 

Quei numeri tra parentesi sono le latenze delle istruzioni ... che il salto è 0 se il predittore del ramo indovina correttamente (che dal momento che per lo più farà il loop per la maggior parte del tempo).

Quindi: mentre la moltiplicazione è ancora in corso (3 istruzioni) torniamo all'inizio del ciclo dopo 2 e proviamo a caricare nella memoria e dobbiamo stallo. Oppure potremmo fare un negozio ... che possiamo fare contemporaneamente alla nostra moltiplicazione e quindi non fermarci affatto.

E il negozio fittizio che chiedi? Perché funziona? Si noti che si sta memorizzando il valore critico che stiamo utilizzando per moltiplicare in memoria. In tal modo il processore può utilizzare questo valore che viene memorizzato in memoria e clobare il registro.

Quindi perché il processore non può farlo comunque? Il processore non può produrre più accessi di memoria di quanti lo chiedi o potrebbe interferire con i programmi multiprocessore (immagina che la linea di cache che stai scrivendo sia condivisa e devi invalidarla su ogni CPU ogni loop scrivendola ... Ahia!).

Tutto questo è pura speculazione, ma sembra corrispondere a tutte le prove (il codice e la mia conoscenza dell'architettura Intel ... e dell'assemblaggio x86). Spero che qualcuno possa far notare se ho qualcosa che non va.