J'ai fait des expériences avec le multithreading et le traitement parallèle et j'avais besoin d'un compteur pour effectuer un comptage de base et une analyse statistique de la vitesse du traitement. Pour éviter les problèmes avec l'utilisation simultanée de ma classe, j'ai utilisé une instruction lock sur une variable privée dans ma classe:
private object mutex = new object();
public void Count(int amount)
{
lock(mutex)
{
done += amount;
}
}
Mais je me demandais ... combien coûte le verrouillage d'une variable? Quels sont les effets négatifs sur les performances?
Voici n article qui va dans le coût. La réponse courte est 50ns.
La réponse technique est que cela est impossible à quantifier, cela dépend fortement de l'état des tampons de réécriture de la mémoire CPU et de la quantité de données que le préfetcher a collectées doit être rejetée et relue. Qui sont tous deux très non déterministes. J'utilise 150 cycles CPU comme une approximation de fond de panier qui évite les déceptions majeures.
La réponse pratique est qu'il est waaaay moins cher que le temps que vous passerez à déboguer votre code lorsque vous pensez pouvoir sauter un verrou.
Pour obtenir un nombre fixe, vous devrez mesurer. Visual Studio a une nappe analyseur de simultanéité disponible comme extension.
Je voudrais présenter quelques-uns de mes articles, qui s'intéressent aux primitives de synchronisation générales et qui creusent dans Monitor, le comportement, les propriétés et les coûts des instructions de verrouillage C # en fonction de scénarios distincts et du nombre de threads. Il s'intéresse spécifiquement au gaspillage du processeur et aux périodes de débit pour comprendre la quantité de travail pouvant être poussée dans plusieurs scénarios:
https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introductionhttps://www.codeproject.com/Articles/1237518/Unified-Concurrency- II-benchmarking-methodologieshttps://www.codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking
Oh cher!
Il semble que la bonne réponse signalée ici comme LA RÉPONSE soit intrinsèquement incorrecte! Je voudrais demander à l'auteur de la réponse, respectueusement, de lire l'article lié jusqu'à la fin. article
L'auteur de l'article de 2003 article mesurait uniquement sur une machine Dual Core et dans le premier boîtier de mesure, il mesurait le verrouillage avec un seul fil et le résultat était d'environ 50 ns par accès verrouillé.
Il ne dit rien sur un verrou dans l'environnement simultané. Nous devons donc continuer à lire l'article et dans la seconde moitié, l'auteur mesurait le scénario de verrouillage avec deux et trois threads, ce qui se rapproche des niveaux de concurrence des processeurs d'aujourd'hui.
Ainsi, l'auteur dit qu'avec deux threads sur Dual Core, les verrous coûtent 120 ns et avec 3 threads, il passe à 180 ns. Il semble donc dépendre clairement du nombre de threads accédant au verrou simultanément.
Donc c'est simple, ce n'est pas 50 ns sauf s'il s'agit d'un seul thread, où le verrou devient inutile.
Un autre problème à considérer est qu'il est mesuré comme temps moyen !
Si le temps des itérations était mesuré, il y aurait même des temps compris entre 1 ms et 20 ms, simplement parce que la majorité était rapide, mais peu de threads attendraient le temps des processeurs et subiraient même des retards de plusieurs millisecondes.
C'est une mauvaise nouvelle pour tout type d'application qui nécessite un débit élevé et une faible latence.
Et le dernier point à considérer est qu'il pourrait y avoir des opérations plus lentes à l'intérieur de la serrure et c'est très souvent le cas. Plus le bloc de code est exécuté à l'intérieur de la serrure, plus la contention est élevée et les retards montent très haut.
Veuillez noter que plus d'une décennie s'est déjà écoulée depuis 2003, c'est-à-dire quelques générations de processeurs conçus spécifiquement pour fonctionner de manière simultanée et le verrouillage nuit considérablement à leurs performances.
Cela ne répond pas à votre question sur les performances, mais je peux dire que le .NET Framework offre un Interlocked.Add
méthode qui vous permettra d'ajouter votre amount
à votre membre done
sans verrouiller manuellement sur un autre objet.
lock
(Monitor.Enter/Exit) est très bon marché, moins cher que des alternatives comme un Waithandle ou Mutex.
Mais si c'était (un peu) lent, préféreriez-vous un programme rapide avec des résultats incorrects?
Le coût d'une serrure dans une boucle étroite, par rapport à une alternative sans serrure, est énorme. Vous pouvez vous permettre de boucler plusieurs fois tout en étant plus efficace qu'une serrure. C'est pourquoi les files d'attente sans verrou sont si efficaces.
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();
}
}
}
Sortie:
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
Il existe plusieurs façons de définir le "coût". Il y a les frais généraux réels d'obtention et de libération du verrou; comme l'écrit Jake, c'est négligeable à moins que cette opération ne soit effectuée des millions de fois.
Plus pertinent est l'effet que cela a sur le flux d'exécution. Ce code ne peut être entré que par un thread à la fois. Si 5 threads effectuent régulièrement cette opération, 4 d'entre eux finiront par attendre la libération du verrou, puis seront le premier thread programmé pour entrer ce morceau de code après la libération de ce verrou. Donc, votre algorithme va souffrir considérablement. Dans quelle mesure cela dépend de l'algorithme et de la fréquence à laquelle l'opération est appelée. Vous ne pouvez pas vraiment l'éviter sans introduire de conditions de concurrence, mais vous pouvez l'améliorer en minimisant le nombre d'appels au code verrouillé.