Domanda

Quanto spesso ti ritrovi effettivamente a utilizzare gli spinlock nel tuo codice?Quanto è comune imbattersi in una situazione in cui l'utilizzo di un ciclo occupato effettivamente supera l'utilizzo dei blocchi?
Personalmente, quando scrivo una sorta di codice che richiede la sicurezza del thread, tendo a confrontarlo con diverse primitive di sincronizzazione e, per quanto riguarda, sembra che l'uso dei blocchi offra prestazioni migliori rispetto all'uso degli spinlock.Non importa per quanto poco tempo mantengo effettivamente il blocco, la quantità di contesa che ricevo quando utilizzo gli spinlock è di gran lunga maggiore della quantità che ottengo utilizzando i blocchi (ovviamente eseguo i miei test su una macchina multiprocessore).

Mi rendo conto che è più probabile che si verifichi uno spinlock nel codice di "basso livello", ma mi interessa sapere se lo trovi utile anche in un tipo di programmazione di livello più alto?

È stato utile?

Soluzione

Dipende da cosa stai facendo.Nel codice dell'applicazione generale, ti consigliamo di evitare gli spinlock.

Nelle cose di basso livello in cui manterrai il blocco solo per un paio di istruzioni e la latenza è importante, un tappetino spinlock è una soluzione migliore di un blocco.Ma questi casi sono rari, soprattutto nel tipo di applicazioni in cui viene generalmente utilizzato C#.

Altri suggerimenti

In C#, gli "spin lock" sono stati, secondo la mia esperienza, quasi sempre peggio che prendere un lock: è raro che gli spin lock superino un lock.

Tuttavia, non è sempre così..NET 4 sta aggiungendo a System.Threading.SpinLock struttura.Ciò offre vantaggi nelle situazioni in cui un blocco viene mantenuto per un tempo molto breve e viene afferrato ripetutamente.Dai documenti MSDN in poi Strutture dati per la programmazione parallela:

Negli scenari in cui si prevede che l'attesa per il blocco sia breve, SpinLock offre prestazioni migliori rispetto ad altre forme di blocco.

Gli spin lock possono sovraperformare altri meccanismi di blocco nei casi in cui stai facendo qualcosa come bloccare un albero: se hai blocchi su ciascun nodo solo per un periodo di tempo molto, molto breve, possono eseguire un blocco tradizionale.Mi sono imbattuto in questo in un motore di rendering con un aggiornamento della scena multithread, a un certo punto: i blocchi di rotazione sono stati profilati per superare il blocco con Monitor.Enter.

Per il mio lavoro in tempo reale, in particolare con i driver dei dispositivi, li ho usati parecchio.Si scopre che (l'ultima volta che l'ho cronometrato) l'attesa di un oggetto di sincronizzazione come un semaforo legato a un'interruzione hardware dura almeno 20 microsecondi, indipendentemente da quanto tempo effettivamente occorre affinché si verifichi l'interruzione.Un singolo controllo di un registro hardware mappato in memoria, seguito da un controllo su RDTSC (per consentire un timeout in modo da non bloccare la macchina) è nell'intervallo dei nannosecondi elevati (sostanzialmente nel rumore).Per l'handshake a livello hardware, che non dovrebbe richiedere molto tempo, è davvero difficile battere uno spinlock.

Il mio 2c:Se i tuoi aggiornamenti soddisfano alcuni criteri di accesso, allora sono buoni candidati spinlock:

  • veloce, ovvero avrai tempo per acquisire lo spinlock, eseguire gli aggiornamenti e rilasciare lo spinlock in un unico thread quanti in modo da non essere bloccato mentre tieni lo spinlock
  • localizzato tutti i dati che aggiorni si trovano preferibilmente in una singola pagina già caricata, non vuoi perdere un TLB mentre tieni premuto lo spinlock e sicuramente non vuoi che venga letto uno scambio di errori di pagina!
  • atomico non è necessario nessun altro lucchetto per eseguire l'operazione, ad es.non aspettare mai i blocchi sotto Spinlock.

Per tutto ciò che ha un potenziale rendimento, dovresti utilizzare una struttura di blocco notificata (eventi, mutex, semafori ecc.).

Un caso d'uso per gli spin lock è se ti aspetti un conflitto molto basso ma ne avrai molti.Se non è necessario il supporto per il blocco ricorsivo, è possibile implementare uno spinlock in un singolo byte e, se il conflitto è molto basso, lo spreco del ciclo della CPU è trascurabile.

Per un caso d'uso pratico, ho spesso array di migliaia di elementi, in cui gli aggiornamenti a diversi elementi dell'array possono avvenire in sicurezza in parallelo.Le probabilità che due thread tentino di aggiornare lo stesso elemento contemporaneamente sono molto piccole (basso conflitto) ma ho bisogno di un blocco per ogni elemento (ne avrò molti).In questi casi, di solito alloco un array di ubyte della stessa dimensione dell'array che sto aggiornando in parallelo e implemento gli spinlock in linea come (nel linguaggio di programmazione D):

while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
    myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);

D'altra parte, se dovessi utilizzare blocchi regolari, dovrei allocare un array di puntatori a Objects e quindi allocare un oggetto Mutex per ciascun elemento di questo array.In scenari come quello sopra descritto, questo è semplicemente uno spreco.

Se disponi di codice critico per le prestazioni E hai stabilito che deve essere più veloce di quanto non sia attualmente E hai determinato che il fattore critico è la velocità di blocco, quindi sarebbe una buona idea provare uno spinlock.In altri casi, perché preoccuparsi?Le serrature normali sono più facili da usare correttamente.

Si prega di notare i seguenti punti:

  1. La maggior parte delle implementazioni di mutex girano per un po' prima che il thread venga effettivamente non pianificato.Per questo motivo è difficile confrontare questi mutex con gli spinlock puri.

  2. Diversi thread che ruotano "il più velocemente possibile" sullo stesso spinlock consumeranno tutta la larghezza di banda e diminuiranno drasticamente l'efficienza del programma.È necessario aggiungere un piccolo tempo di "sonno" aggiungendo noop nel ciclo di rotazione.

Non è quasi mai necessario utilizzare gli spinlock nel codice dell'applicazione, semmai è meglio evitarli.

Non riesco a trovare alcun motivo per utilizzare uno spinlock nel codice C# in esecuzione su un sistema operativo normale.I blocchi occupati sono per lo più uno spreco a livello di applicazione: la rotazione può causare l'utilizzo dell'intero intervallo di tempo della CPU, mentre un blocco causerà immediatamente un cambio di contesto, se necessario.

Il codice ad alte prestazioni in cui hai nr di thread=nr di processori/core potrebbe trarne vantaggio in alcuni casi, ma se hai bisogno di ottimizzazione delle prestazioni a quel livello probabilmente realizzerai un gioco 3D di prossima generazione, lavorando su un sistema operativo incorporato con primitive di sincronizzazione scarse, creando un Sistema operativo/driver o comunque non utilizzando c#.

Ho usato gli spin lock per la fase stop-the-world del garbage collector nel mio HLVM project perché sono facili e questa è una VM giocattolo.Tuttavia, gli spin lock possono essere controproducenti in questo contesto:

Uno dei bug di perf nel garbage collector del Glasgow Haskell Compiler è così fastidioso che ha un nome, "ultimo rallentamento del core".Questa è una conseguenza diretta dell'uso inappropriato degli spinlock nel loro GC ed è esacerbata su Linux a causa del suo scheduler ma, di fatto, l'effetto può essere osservato ogni volta che altri programmi competono per il tempo della CPU.

L'effetto è chiaro nel secondo grafico Qui e si può vedere che colpisce più del semplice ultimo nucleo Qui, dove il programma Haskell vede un degrado delle prestazioni oltre i soli 5 core.

Tieni sempre a mente questi punti durante l'utilizzo spinlock:

  • Esecuzione rapida in modalità utente.
  • Sincronizza i thread all'interno di un singolo processo o di più processi se nella memoria condivisa.
  • Non ritorna finché l'oggetto non viene posseduto.
  • Non supporta la ricorsione.
  • Consuma il 100% della CPU durante l'"attesa".

Personalmente ho visto così tanti deadlock solo perché qualcuno pensava che sarebbe stata una buona idea usare lo spinlock.

Stai molto molto attento mentre usi gli spinlock

(Non posso sottolinearlo abbastanza).

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