2011-01-12 27 views
73

Ho sperimentato con il multi threading e l'elaborazione parallela e avevo bisogno di un contatore per fare alcune analisi di conteggio e statistiche base della velocità dell'elaborazione. Per evitare problemi con l'uso concomitante di mia classe ho usato una dichiarazione di blocco su una variabile privata nella mia classe:Quanto costa la dichiarazione di blocco?

private object mutex = new object(); 

public void Count(int amount) 
{ 
lock(mutex) 
{ 
    done += amount; 
} 
} 

Ma mi chiedevo ... Quanto costa la chiusura di una variabile? Quali sono gli effetti negativi sulla performance?

+7

Il blocco della variabile non è così costoso; è l'attesa su una variabile bloccata che vuoi evitare. – Gabe

+34

è molto meno costoso che passare ore a rintracciare un'altra condizione di gara ;-) – BrokenGlass

+1

Beh ... se una serratura è costosa potresti volerli evitare modificando la programmazione in modo che richieda meno blocchi. Potrei implementare un qualche tipo di sincronizzazione. –

risposta

63

Ecco il costo di an article. La risposta breve è 50ns.

+1

Quindi, in conclusione, più oggetti hai, più diventa costoso. –

+13

Breve risposta migliore: 50ns + tempo trascorso in attesa se l'altro thread è in blocco. – Herman

+2

Più thread stanno entrando e lasciando il lock, più diventa costoso. Il costo si espande in modo esponenziale con il numero di thread –

17

Questo non risponde alla vostra domanda circa le prestazioni, ma posso dire che il .NET Framework non offre un metodo Interlocked.Add che vi permetterà di aggiungere il amount al done membro senza bloccare manualmente su un altro oggetto.

+1

Sì, questa è probabilmente la risposta migliore. Ma principalmente per motivi di codice più breve e più pulito. La differenza di velocità non è probabile che sia evidente. –

+0

grazie per questa risposta. Sto facendo più cose con le serrature. Inserti aggiunti è uno dei tanti. Adora il suggerimento, lo userà d'ora in poi. I blocchi –

+0

sono molto, molto più facili da ottenere, anche se il codice di blocco è potenzialmente più veloce. Interlocked.Add ha gli stessi problemi di + = senza sincronizzazione. – hangar

9

lock (Monitor.Enter/Exit) è molto economico, più economico di alternative come Waithandle o Mutex.

Ma se fosse (un po ') lento, preferireste avere un programma veloce con risultati errati?

+4

Haha ... Stavo andando al programma veloce e ai buoni risultati. –

+0

@ henk-holterman Ci sono diversi problemi con le tue affermazioni: ** Primo ** come questa domanda e le risposte hanno mostrato chiaramente, c'è scarsa comprensione dell'impatto del blocco sulla performance generale, anche le persone che affermano il mito di 50ns che è applicabile solo con ambiente a thread singolo. ** Secondo ** la tua affermazione è qui e rimarrà per anni e nel frattempo, processori cresciuti in core, ma la velocità dei core non è così elevata. ** Le applicazioni di Thrid ** diventano solo più complesse nel tempo, e quindi è strato su strato di bloccaggio in ambiente di molti core e il numero è in aumento, 2,4,8,10,20,16,32 – ipavlu

+0

Il mio approccio abituale è quello di costruire la sincronizzazione in modo libero e con il minor numero possibile di interazione. Ciò si traduce in strutture di dati prive di lock. Ho realizzato i wrapper di codice attorno allo spinlock per semplificare lo sviluppo e anche quando TPL ha raccolte concorrenti speciali, ho sviluppato raccolte chiuse a rotazione di mie liste, array, dizionari e code, poiché avevo bisogno di un controllo leggermente maggiore e qualche volta di codice in esecuzione spinlock. Posso dirti, è possibile e consente di risolvere più scenari che le raccolte TPL non possono fare e con grandi prestazioni/guadagno. – ipavlu

4

Ci sono diversi modi per definire "costo". C'è il sovraccarico effettivo di ottenere e rilasciare la serratura; come scrive Jake, è trascurabile a meno che questa operazione non venga eseguita milioni di volte.

Di maggiore rilevanza è l'effetto che questo ha sul flusso di esecuzione. Questo codice può essere inserito solo da un thread alla volta. Se hai 5 thread che eseguono questa operazione su base regolare, 4 di questi finiranno per aspettare che il blocco venga rilasciato, e quindi di essere il primo thread programmato per inserire quel pezzo di codice dopo che il blocco è stato rilasciato. Quindi il tuo algoritmo soffrirà in modo significativo. Quanto dipende dall'algoritmo e dalla frequenza con cui viene chiamata l'operazione. Non puoi davvero evitarlo senza introdurre condizioni di gara, ma puoi migliorarlo riducendo al minimo il numero di chiamate al codice bloccato.

43

La risposta tecnica è che è impossibile quantificare, dipende in gran parte dallo stato dei buffer di write-back della memoria della CPU e dalla quantità di dati che il prefetcher ha raccolto deve essere scartato e riletto. Che sono entrambi molto non deterministici. Uso 150 cicli della CPU come un'approssimazione "dietro l'involucro" che evita grosse delusioni.

La risposta pratica è che è waaaay più economico della quantità di tempo che si brucia sul debug del codice quando si pensa di poter saltare un blocco.

Per ottenere un numero rigido, è necessario misurare. Visual Studio ha un elegante concurrency analyzer disponibile come estensione.

+1

In realtà no, può essere quantificato e misurato. Semplicemente non è così facile come scrivere quei blocchi attorno al codice, quindi affermare che si tratta solo di 50ns, un mito misurato su un singolo accesso al blocco. – ipavlu

+4

* "pensa di poter saltare un lucchetto" * ... Penso che sia dove si trovano molte persone quando leggono questa domanda ... – Snoopy

6

Il costo per una serratura in un circuito chiuso, rispetto a un'alternativa senza serratura, è enorme. Puoi permetterti di fare il loop molte volte e di essere ancora più efficiente di un lucchetto. Ecco perché bloccare le code libere sono così efficienti.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace LockPerformanceConsoleApplication 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var stopwatch = new Stopwatch(); 
      const int LoopCount = (int) (100 * 1e6); 
      int counter = 0; 

      for (int repetition = 0; repetition < 5; repetition++) 
      { 
       stopwatch.Reset(); 
       stopwatch.Start(); 
       for (int i = 0; i < LoopCount; i++) 
        lock (stopwatch) 
         counter = i; 
       stopwatch.Stop(); 
       Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds); 

       stopwatch.Reset(); 
       stopwatch.Start(); 
       for (int i = 0; i < LoopCount; i++) 
        counter = i; 
       stopwatch.Stop(); 
       Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds); 
      } 

      Console.ReadKey(); 
     } 
    } 
} 

uscita:

With lock: 2013 
Without lock: 211 
With lock: 2002 
Without lock: 210 
With lock: 1989 
Without lock: 210 
With lock: 1987 
Without lock: 207 
With lock: 1988 
Without lock: 208 
+3

Questo potrebbe essere un cattivo esempio perché il tuo ciclo non fa davvero niente, a parte da una singola assegnazione di variabile e un blocco sono almeno 2 chiamate di funzione. Inoltre, 20ns per blocco che stai ottenendo non è poi così male. –

22

Oh dear!

Sembra che la risposta corretta contrassegnata qui come LA RISPOSTA sia intrinsecamente scorretta! Vorrei chiedere all'autore della risposta, rispettosamente, di leggere l'articolo collegato fino alla fine.article

L'autore dell'articolo dal 2003 article misurava solo su macchina Dual Core e nel primo caso di misura, si misurata bloccaggio con attacco singolo solo e il risultato era di circa 50 ns per l'accesso serratura.

Non dice nulla su un blocco nell'ambiente concorrente. Quindi dobbiamo continuare a leggere l'articolo e nella seconda metà l'autore stava misurando lo scenario di blocco con due e tre thread, che si avvicina ai livelli di concorrenza dei processori odierni.

Quindi l'autore dice che con due thread su Dual Core, i blocchi costano 120ns e con 3 thread si passa a 180ns. Quindi sembra essere chiaramente dipendente dal numero di thread ad accesso simultaneo e più è peggio.

Quindi è semplice, non è 50 ns, a meno che non sia un singolo thread, in cui il blocco diventa inutile.

Un altro problema da considerare è che viene misurato come tempo medio !

Se il tempo di iterazioni sarebbe misurata, ci sarebbe anche più volte tra 1ms a 20ms, semplici perché la maggioranza era veloce, ma pochi fili saranno in attesa di tempo processori e incorrere anche millisecondi lunghi ritardi.

Questa è una cattiva notizia per qualsiasi tipo di applicazione che richiede un throughput elevato, bassa latenza.

E l'ultimo problema da considerare è che potrebbero esserci operazioni più lente all'interno della serratura e molto spesso questo è il caso. Quanto più lungo il blocco di codice viene eseguito all'interno del blocco, tanto maggiore è la contesa e i ritardi salgono in alto.

Si consideri che oltre un decennio è passato già dal 2003, cioè poche generazioni di processori progettati specificamente per funzionare in modo simultaneo e bloccante sta danneggiando notevolmente le loro prestazioni.

Problemi correlati