2009-05-12 4 views
21

Stavo eseguendo il debug di un'applicazione multi-thread e ho trovato la struttura interna di CRITICAL_SECTION. Ho trovato un membro dei dati LockSemaphore di CRITICAL_SECTION uno interessante.La sezione critica è sempre più veloce?

Sembra LockSemaphore è un evento reset automatico (non un semaforo come suggerisce il nome) e il sistema operativo crea questo evento in silenzio quando prima volta che un thread attende su Critcal Section che è bloccato da qualche altro thread.

Ora, mi chiedo è sezione critica sempre più veloce? Event è un oggetto kernel e ogni oggetto Section critico è associato all'oggetto evento, quindi come Critical Section può essere più veloce rispetto ad altri oggetti kernel come Mutex? Inoltre, in che modo l'oggetto evento interno influenza effettivamente le prestazioni della sezione Critico?

Ecco la struttura della CRITICAL_SECTION:

struct RTL_CRITICAL_SECTION 
{ 
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo; 
    LONG LockCount; 
    LONG RecursionCount; 
    HANDLE OwningThread; 
    HANDLE LockSemaphore; 
    ULONG_PTR SpinCount; 
}; 
+2

Ricordare inoltre che i dettagli di implementazione di CriticalSection possono variare da versione a versione del sistema operativo. Può essere istruttivo osservare questi dettagli, ma non fare affidamento su di essi, poiché potrebbero cambiare. –

+0

Sicuramente è un semaforo in NT5 – paulm

risposta

34

Quando dicono che una sezione critica è "veloce", significa "è conveniente acquistarne una quando non è già bloccata da un'altra discussione".

[Si noti che se si è già bloccato da un altro thread, quindi non importa quasi tanto come veloce è.]

Il motivo per cui è veloce perché, prima di andare nel kernel utilizza l'equivalente di InterlockedIncrement in uno di quei campi LONG (forse nel campo LockCount) e se riesce, allora considera il blocco acquisito senza essere entrato nel kernel.

L'API InterlockedIncrement è implementata in modalità utente come un codice operativo "LOCK INC" ... in altre parole è possibile acquisire una sezione critica non contestata senza eseguire alcuna transizione di squillo nel kernel.

+1

+1 Bella spiegazione. Effettivamente puoi metterti alla prova se riesci ad acquisire la sezione critica leggendo LockCount. – Arno

+0

@Arno: c'è ['TryEnterCriticalSection'] (https://msdn.microsoft.com/en-us/library/windows/desktop/ms686857%28v=vs.85%29.aspx) per quello. – rwong

3

Le CriticalSections gireranno un breve periodo (alcuni ms) e continuare a controllare se il blocco è libero. Dopo che il numero di giri è scaduto, ricadrà all'evento del kernel. Quindi, nel caso in cui il titolare del blocco scappi rapidamente, non dovrai mai effettuare la transizione costosa al codice del kernel.

EDIT: andarono e trovarono alcuni commenti nel mio codice: a quanto pare il MS Heap Manager utilizza un conteggio di rotazione di 4000 (incrementi interi, non ms)

+1

4 secondi non è un po 'troppo? Inoltre, l'oggetto evento viene creato nel momento in cui il sistema operativo rileva che la sezione critica è bloccata. è un po 'sovraccarico, giusto? –

+0

wow, 4 secondi è enorme in termini di computer ... hai forse inteso 4000 microsecondi (cioè 4 ms)? Non penso che un interruttore di contesto impieghi anche 4ms su una macchina moderna. Potete fornire una citazione della cifra 4sec? – rmeador

+4

In realtà, il valore del numero di spin è solo un conteggio, non è un valore espresso in unità di tempo. Il valore di 4.000 deriva dalla documentazione MSDN per InitializeCriticalSectionAndSpinCount(). Nota che questo potrebbe cambiare da versione a versione. I documenti dicono "circa 4000". – Foredecker

25

Nel lavoro le prestazioni, alcune cose rientrano nella categoria "sempre" :) Se implementi qualcosa di simile a una sezione critica del sistema operativo usando altre primitive, le probabilità sono più lente nella maggior parte dei casi.

Il modo migliore per rispondere alla domanda è con le misurazioni delle prestazioni. Come funzionano gli oggetti OS è molto dipendente dallo scenario. Ad esempio, le sezioni critiche sono generalmente considerate "veloci" se la contesa è bassa. Sono anche considerati veloci se il tempo di blocco è inferiore al tempo di conteggio degli spin.

La cosa più importante per determinare se è contesa su una sezione critica è il primo ordine fattore limitante nell'applicazione. In caso contrario, utilizzare semplicemente una sezione critica normalizzata e lavorare sul collo di bottiglia primario (o sul collo) delle applicazioni.

Se le prestazioni sezione critica è critica, allora si può considerare quanto segue.

  1. Impostare con attenzione il conteggio dei blocchi spin per le sezioni critiche "calde". Se la prestazione è fondamentale, allora il lavoro qui vale la pena. Ricorda, mentre lo spin lock evita la modalità utente per la transizione del kernel, consuma il tempo della CPU a un ritmo furioso - mentre gira, nient'altro può usare quel tempo della CPU. Se un blocco viene tenuto premuto per un tempo sufficiente, il thread di rotazione bloccherà effettivamente, liberando la CPU per svolgere altri compiti.
  2. Se si dispone di un modello lettore/scrittore, prendere in considerazione l'utilizzo di Slim Reader/Writer (SRW) locks. Il lato negativo qui è che sono disponibili solo su Vista e Windows Server 2008 e prodotti successivi.
  3. Si può essere in grado di utilizzare condition variables con la sezione critica per ridurre al minimo il polling e contese, veglia discussioni solo quando necessario. Di nuovo, questi sono supportati su Vista e Windows Server 2008 e prodotti successivi.
  4. Considerare l'utilizzo di Interlocked Singly Linked Lists (SLIST): questi sono efficienti e "bloccati". Ancora meglio, sono supportati su XP e Windows Server 2003 e prodotti successivi.
  5. Esaminare il codice: potrebbe essere possibile interrompere un blocco "attivo" rifattorizzando un codice e utilizzando un'operazione interbloccata o SLIST per la sincronizzazione e la comunicazione.

In sintesi, gli scenari di ottimizzazione con contesa di blocco possono essere impegnativi (ma interessanti!). Concentrati sulla misurazione delle prestazioni delle tue applicazioni e sulla comprensione dei percorsi caldi. Gli strumenti xperf nel Windows Performance Tool kit sono tuoi amici qui :) Abbiamo appena rilasciato la versione 4.5 nel Microsoft Windows SDK per Windows 7 e .NET Framework 3.5 SP1 (ISO is here, web installer here). È possibile trovare il forum per gli strumenti xperf here. V4.5 supporta pienamente Win7, Vista, Windows Server 2008 - tutte le versioni.

4

CriticalSections è più veloce, ma InterlockedIncrement/InterlockedDecrement è più. Vedi questo esempio di utilizzo dell'implementazione LightweightLock full copy.

+0

Il collegamento è morto, è possibile che si desideri modificare la risposta – Moe

+1

LightWeightLock potrebbe essere più lento poiché è uno spinlock. Può anche bloccarsi in caso di inversione di priorità. –

+0

Tendo ad usare le funzioni interbloccate (in particolare InterlockedCompareExchange) per proteggere piccoli blocchi di codice che non chiamano altre funzioni. Nel mio caso ho notato che le funzioni interbloccate erano due volte più veloci delle funzioni di CriticalSection (che era molto importante nel mio caso poiché si trattava di un piccolo blocco che veniva eseguito molto spesso). Lo svantaggio delle funzioni interbloccate è che non è possibile nidificarle e si possono accidentalmente usarle male (ad esempio bloccare in una discussione, sbloccare in un'altra discussione). – Patrick

1

Ecco un modo di vedere le cose:

Se non c'è conflitto, poi il blocco di spin è veramente veloce rispetto ad andare modalità kernel per un mutex.

Quando c'è contesa, un CriticalSection è leggermente più costoso rispetto all'utilizzo diretto di un Mutex (a causa del lavoro extra per rilevare lo stato di spinlock).

Quindi si riduce a una media ponderata, in cui i pesi dipendono dalle specifiche del proprio modello di chiamata. Detto questo, se hai poca contesa, allora una CriticalSection è una grande vittoria. Se, d'altra parte, hai sempre un sacco di contesa, allora pagherai una penalità molto piccola sull'uso diretto di un Mutex. Ma in tal caso, ciò che guadagneresti passando a un Mutex è piccolo, quindi probabilmente starai meglio a cercare di ridurre la contesa.

+0

Imposta lo SpinCount su 0 e almeno non è mai più lento di un mutex anche sotto pesante contesa. – Lothar

+0

Haha, ed è lo stesso di un mutex! –

1

La sezione critica è più veloce del mutex perché perché la sezione critica non è un oggetto kernel. Questo fa parte della memoria globale del processo attuale. Il Mutex risiede effettivamente nel Kernel e la creazione di un oggetto mutext richiede uno switch del kernel, ma in caso di sezione critica no. Anche se la sezione critica è veloce, ci sarà uno switch del kernel mentre si usa la sezione critica quando i thread stanno per aspettare. Questo perché la programmazione dei thread avviene nel kernel.