2014-04-02 18 views
8
Aggiornamento

- Ho trovato la causa di lock() consumando cicli di CPU come un matto, ho aggiunto queste informazioni dopo la mia domanda originale. Tutto questo si è rivelato essere un muro di testo in modo da:C# lock statement performance

TL; DR Il C# built-in lock() meccanismo sarà, in alcune circostanze, utilizzare una insolita quantità di tempo di CPU se il sistema è in esecuzione con un alto timer del sistema di risoluzione.

domanda originale:

Ho un'applicazione che accedere a una risorsa da più thread. La risorsa è un dispositivo collegato a USB. È una semplice interfaccia di comando/risposta e io uso un piccolo blocco lock() per garantire che anche il thread che invia un comando ottenga la risposta. mio implementazione utilizza la parola blocco (obj):

lock (threadLock) 
{ 
    WriteLine(commandString); 
    rawResponse = ReadLine(); 
} 

Quando accedo questo da 3 discussioni più velocemente possibile (in un loop stretto) l'utilizzo della CPU è di circa il 24% su un computer di fascia alta. A causa della natura della porta USB, vengono eseguite solo circa 1000 operazioni di comando/risposta al secondo. Poi ho implementato il meccanismo di blocco qui descritto SimpleExclusiveLock e il codice ora simile a questa (alcune cose try/catch per rilasciare il blocco in caso di un'eccezione I/O viene rimosso):

Lock.Enter(); 
WriteLine(commandString); 
rawResponse = ReadLine(); 
Lock.Exit(); 

Utilizzando questa applicazione l'utilizzo della CPU scende a < l'1% con lo stesso programma di test a 3 thread mentre si ottengono ancora le 1000 operazioni di comando/risposta al secondo.

La domanda è: qual è, in questo caso, il problema utilizzando la parola chiave lock() integrata?

Ho incidentalmente imbattuto in un caso in cui il meccanismo lock() ha un overhead eccezionalmente alto? La filettatura che entra nella sezione critica manterrà il blocco solo per circa 1 ms.

Aggiornamento: La causa di lock() mangiare CPU come un matto è che alcune applicazioni ha aumentato la risoluzione del timer per l'intero sistema utilizzando timeBeginPeriod() in Winmm.dll. I colpevoli nel mio caso sono Google Chrome e SQL Server - hanno chiesto risoluzione timer di sistema 1 ms utilizzando:

[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)] 
private static extern uint TimeBeginPeriod(uint uMilliseconds); 

Ho trovato questo tramite funzione powercfg:

powercfg -energy duration 5 

causa di una sorta di difetto di progettazione nella dichiarazione integrata lock() questo aumento della risoluzione del timer mangia CPU come un matto (almeno nel mio caso). Quindi, ho ucciso i programmi che richiedono il timer di sistema ad alta risoluzione. La mia applicazione ora funziona un po 'più lentamente. Ogni richiesta verrà bloccata per 16,5 ms anziché 1 ms. Il motivo dietro quello che immagino è che i thread sono programmati meno frequentemente. Anche l'utilizzo della CPU (come mostrato in Task Manager) è sceso a zero. Non ho dubbi che lo lock() usi ancora parecchi cicli, ma ora è nascosto.

Nel mio progetto l'utilizzo ridotto della CPU è un fattore di progettazione importante. Anche la bassa latenza di 1 ms delle richieste USB è positiva per la progettazione generale.Quindi (nel mio caso) la soluzione è scartare lo lock() integrato e sostituirlo con un meccanismo di blocco correttamente implementato. Ho già buttato fuori l'imperfetto System.IO.Ports.SerialPort in favore di WinUSB quindi non ho timori :)

Ho fatto una piccola console-applicazione per dimostrare tutto questo, me se sono interessato a una copia (~ 100 righe di codice).

immagino ho risposto alla mia domanda in modo I'll basta lasciare questo qui nel caso in cui qualcuno è interessato ...

+1

Mi intriga nel fatto che l'articolo collegato usi 'Semaphore' piuttosto che' lock' per l'attesa; che * dovrebbe * essere più costoso (ha bisogno di andare al livello del sistema operativo, ecc.). Per inciso, "solo circa 1 ms": 1 ms è un ** tempo molto lungo ** per un computer. –

+0

Oh sì Marc, il "solo 1 ms" deriva dal presupposto che 1 ms è un tempo breve per lo scheduler dei thread (afaik) – Mikael

+0

Henk, la quantità di lavoro è piccola. Ma il "dispositivo" è una macchina complessa. Avevo l'impressione di poter creare una serie di thread per controllare varie parti (separate) della macchina senza attese inutili. Sono possibili solo 1000 operazioni di I/O di controllo al secondo, quindi la maggior parte dei thread attenderà l'acquisizione del blocco. Apparentemente, questo è un cattivo design usando il built-in lock() ma un buon design se si passa a un meccanismo di blocco più economico :) È quasi come il built in lock() utilizza una sorta di attesa? – Mikael

risposta

5

No, mi dispiace, questo non è possibile. Non c'è scenario in cui si hanno 3 thread con 2 di essi bloccati sul blocco e 1 blocco su un'operazione I/O che richiede un millisecondo può ottenere il 24% di utilizzo della cpu. L'articolo collegato è forse interessante, ma la classe .NET Monitor fa esattamente la stessa cosa. Compresa l'ottimizzazione di CompareExchange() e la coda di attesa.

L'unico modo per ottenere il 24% è tramite altro codice che viene eseguito nel programma. Con il comune stealer del ciclo è il thread dell'interfaccia utente che ti colpisce a migliaia di volte al secondo. Molto semplice da masterizzare in questo modo. Un errore classico, gli occhi umani non riescono a leggere così velocemente. Con l'ulteriore estrapolazione, hai quindi scritto un programma di test che non aggiorna l'interfaccia utente. E così non brucia core.

Un profiler ti dirà esattamente dove vanno questi cicli. Dovrebbe essere il tuo prossimo passo.

+0

Il profiler mostra che Monitor.Enter utilizza una quantità eccezionalmente elevata di cicli. Ciò è probabilmente dovuto al fatto che il mio sistema è in esecuzione con una pianificazione dei thread di 1 ms combinata con una sorta di difetto di progettazione in lock(). Ho aggiunto le mie conclusioni nella domanda originale. – Mikael

+0

Non c'è niente di speciale nella "schedulazione dei thread 1 ms", basta usare Chrome. Chiama anche timeBeginPeriod (1). Molti utenti SO preferiscono navigare SO con Chrome, non si lamentano mai di un bug in Monitor.Enter o lock. Non sto comprando. Devi portare la tua affermazione all'assistenza Microsoft. –