Pourquoi l'état partagé dégrade-t-il les performances?

19

Je travaille selon le principe du partage de la programmation simultanée. Essentiellement, tous mes threads de travail ont des copies immuables en lecture seule du même état qui n'est jamais partagé entre eux ( même par référence ). D'une manière générale, cela a très bien fonctionné.

Maintenant, quelqu'un a introduit un cache singleton sans verrouillage ( par exemple un dictionnaire statique ) auquel tous les threads accèdent simultanément. Étant donné que le dictionnaire n'est jamais modifié après le démarrage, il n'y a pas de verrous. Il n'y a eu aucun problème de sécurité des threads, mais il y a maintenant une dégradation des performances.

La question est ... puisqu'il n'y a pas de verrous pourquoi l'introduction de ce singleton crée-t-elle un succès de performance? Que se passe-t-il exactement sous les couvertures qui pourrait expliquer cela?

Pour confirmer, l'accès à ce nouveau singleton est le seul changement et je peux le recréer de manière fiable simplement en commentant l'appel au cache.

JoeGeeky
la source
8
Avez-vous pointé un profileur sur le code?
Timo Geusch,
2
Il est peu probable que le profilage réponde à cette question à moins que vous ne profiliez le CLR et éventuellement le noyau Windows (ce n'est pas une tâche facile pour le programmeur moyen).
Igby Largeman
1
@JoeGeeky Très bien alors, je suppose que la seule chose à faire pour moi ici est +1 et de favoriser! Cela semble étrange car ils sont tous les deux au même niveau d'indirection après tout, et devraient de toute façon tenir dans le cache du processeur, etc ...
Max
2
FWIT J'ai généré quelques threads et exécuté quelques minuteries. J'ai instancié une classe, singleton, verrouilléSingleton et dict <chaîne, chaîne>. Après la première instanciation de chacun, les exécutions consécutives ont pris environ 2000 ns pour un objet donné. Le dictionnaire a fonctionné 2 fois plus lentement, peut-être à cause du code constructeur ... il est plus lent que verrouiller en lui-même. Compte tenu de tous les GC, la gestion du système d'exploitation des files d'attente de threads et autres frais généraux ... pas sûr que l'on puisse vraiment répondre à cette question. Mais d'après mes résultats, je ne pense pas que le problème soit lié aux singletons. Pas s'il est implémenté comme sur MSDN. Exclut les optimisations du compilateur.
P.Brian.Mackey
1
@JoeGeeky - une autre pensée: l'utilisation du cache ajoute-t-elle un niveau d'indirection? Si vous y accédez fréquemment, la poursuite d'un deref de pointeur supplémentaire (ou équivalent MSIL) peut ajouter du temps sur une copie locale moins indirecte.
sdg

Réponses:

8

Il se pourrait que l'état immuable partage une ligne de cache avec quelque chose de mutable. Dans ce cas, une modification de l'état mutable proche pourrait avoir pour effet de forcer une resynchronisation de cette ligne de cache sur les cœurs, ce qui pourrait ralentir les performances.

Aidan Cully
la source
3
Cela ressemble à un false sharingscénario que vous décrivez. Pour isoler ce dont j'ai besoin pour profiler le cache L2. Malheureusement, ce sont des types de référence, donc l'ajout d'espace tampon ne sera pas une option si c'est ce qui se passe réellement.
JoeGeeky
3

Je m'assurerais que les méthodes Equals()et GetHashCode()des objets que vous utilisez comme clés du dictionnaire n'ont pas d'effets secondaires inattendus non adaptés aux threads. Le profilage serait très utile ici.

Si par hasard vos clés sont des chaînes, alors peut-être que vous l'avez: la rumeur veut que les chaînes se comportent comme des objets immuables, mais pour certaines optimisations, elles sont implémentées en interne de manière modifiable, avec tout ce que cela implique en matière de multithreading .

J'essaierais de passer le dictionnaire aux threads qui l'utilisent comme référence régulière au lieu d'un singleton pour voir si le problème réside dans le partage ou la singletonness du dictionnaire. (Éliminer les causes possibles.)

J'essaierais également avec un ConcurrentDictionaryau lieu d'un régulier Dictionaryjuste au cas où son utilisation donnerait des résultats surprenants. Il y a beaucoup de choses à spéculer sur le problème à résoudre si un résultat ConcurrentDictionarys'avère bien meilleur ou bien pire que votre habitué Dictionary.

Si rien de ce qui précède ne pointe vers le problème, je suppose que les performances dégradées sont causées par une sorte de conflit étrange entre le thread de collecte des ordures et le reste de vos threads, car le garbage collector essaie de déterminer si les objets de votre dictionnaire doivent être supprimés ou non, pendant qu'ils sont accessibles par vos threads.

Mike Nakis
la source