2012-10-15 11 views
37

Sto provando a creare una versione stupida di uno spin lock. Navigando sul web, mi sono imbattuto in un'istruzione di assemblaggio chiamata "PAUSE" in x86, che è usata per dare un suggerimento a un processore su cui è attualmente in esecuzione uno spin-lock su questa CPU. Il manuale Intel e informazioni di stato disponibili cheQual è lo scopo dell'istruzione "PAUSE" in x86?

Il processore utilizza questo suggerimento per evitare la violazione dell'ordine memoria maggior parte delle situazioni, che migliora notevolmente le prestazioni del processore. Per il motivo , si consiglia di inserire un'istruzione PAUSE in tutti i cicli di attesa di spin. La documentazione menziona anche che "wait (some delay)" è la pseudo implementazione dell'istruzione.

L'ultima riga del paragrafo precedente è intuitiva. Se non riesco ad afferrare il lucchetto, devo aspettare un po 'prima di afferrare di nuovo il lucchetto.

Tuttavia, che cosa intendiamo per violazione di ordine di memoria in caso di spin lock? Per "violazione dell'ordine di memoria" si intende l'errato speculativo carico/archivio delle istruzioni dopo spin-lock?

La domanda spin-lock è stata chiesta su Stack overflow before ma la domanda di violazione dell'ordine di memoria rimane senza risposta (almeno per la mia comprensione).

risposta

58

Provate a immaginare, come il processore eseguirà un tipico spin-wait ciclo:

1 Spin_Lock: 
2 CMP lockvar, 0 ; Check if lock is free 
3 JE Get_Lock 
4 JMP Spin_Lock 
5 Get_Lock: 

Dopo poche iterazioni del predittore ramo si prevedere che il ramo condizionale (3) non sarà mai preso e il gasdotto compilare con le istruzioni CMP (2). Questo va avanti fino a quando un altro processore scrive uno zero su lockvar. A questo punto abbiamo la pipeline piena di istruzioni CMP speculative (cioè non ancora impegnate) alcune delle quali già leggono lockvar e riportano un (non corretto) risultato diverso da zero al seguente ramo condizionale (3) (anche speculativo). Questo è quando si verifica la violazione dell'ordine di memoria. Ogni volta che il processore "vede" una scrittura esterna (una scrittura da un altro processore), cerca nella sua pipeline le istruzioni che hanno accesso speculativamente alla stessa locazione di memoria e non hanno ancora eseguito il commit. Se vengono trovate tali istruzioni, lo stato speculativo del processore non è valido e viene cancellato con un flusso di pipeline.

Sfortunatamente questo scenario si ripeterà (molto probabilmente) ogni volta che un processore è in attesa su uno spin-lock e rende questi blocchi molto più lenti di quanto dovrebbero essere.

immesso l'istruzione PAUSE:

1 Spin_Lock: 
2 CMP lockvar, 0 ; Check if lock is free 
3 JE Get_Lock 
4 PAUSE   ; Wait for memory pipeline to become empty 
5 JMP Spin_Lock 
6 Get_Lock: 

L'istruzione PAUSE si "de-gasdotto" la memoria si legge, in modo che la pipeline non è compilato con speculativa CMP (2) istruzioni come nel primo esempio. (Vale a dire che potrebbe bloccare la pipeline fino a quando tutte le vecchie istruzioni di memoria non vengono commesse.) Poiché le istruzioni CMP (2) vengono eseguite in sequenza è improbabile (ovvero la finestra temporale è molto più breve) che una scrittura esterna si verifica dopo l'istruzione CMP (2) letta lockvar ma prima che il CMP sia impegnato.

Naturalmente "de-pipelining" consumerà anche meno energia nella spin-lock e in caso di hyperthreading non sprecherà risorse che l'altro thread potrebbe utilizzare meglio. D'altra parte c'è ancora una mis-prediction di ramo in attesa di verificarsi prima di ogni uscita del ciclo. La documentazione di Intel non suggerisce che PAUSE elimini quel flusso di gasdotti, ma chi lo sa ...

+0

(+1) Grazie per l'ottima risposta! Quello che non capisco pienamente è che cosa rende un gasdotto un costo così importante in questa situazione, dato che tutte quelle letture speculative e i rami condizionali presi speculativamente sono assolutamente inutili comunque? Inoltre, c'è un modo per quantificare il costo del flush? – NPE

+4

@NPE Il tempo di recupero da un flush dipende dalla microarchitettura. Processori con pipeline più lunghe (come Core 2) ovviamente soffrono più di quelli con pipeline più brevi (come Atom). Tuttavia, nel caso di un processore con hyperthreading tutte le istruzioni "inutilmente" eseguite rimuovono risorse dall'altro thread sullo stesso core. L'istruzione PAUSE essenzialmente cede la cpu all'altro thread. Pertanto, mentre il costo per il thread bloccato è "solo", due flussi di pipeline, il costo per l'altro thread può essere molto più significativo (a seconda di quanto tempo è trascorso all'interno del blocco). –

+0

Poiché le istruzioni CMP (2) vengono eseguite in sequenza, è improbabile (ovvero la finestra temporale è molto più breve) che una scrittura esterna si verifica dopo che l'istruzione CMP (2) ha letto lockvar ma prima che il CMP sia impegnato. Potresti spiegarlo per favore? Cosa intendi per commit? – KodeWarrior