Warum beeinträchtigt der gemeinsame Status die Leistung?

19

Ich habe nach dem Prinzip der gemeinsamen Programmierung gearbeitet. Grundsätzlich haben alle Worker-Threads unveränderliche Nur-Lese-Kopien desselben Status, die ( auch nach Verweis ) nie zwischen ihnen geteilt werden . Im Allgemeinen hat dies sehr gut funktioniert.

Jetzt hat jemand einen No-Lock-Singleton-Cache ( z. B. ein statisches Wörterbuch ) eingeführt, auf den alle Threads gleichzeitig zugreifen. Da das Wörterbuch nach dem Start nie geändert wird, gibt es keine Sperren. Es gab keine Thread-Sicherheitsprobleme, aber jetzt ist ein Leistungsabfall zu verzeichnen.

Die Frage ist ... da es keine Sperren gibt, warum sorgt die Einführung dieses Singleton für einen Performance-Hit? Was genau ist unter dem Deckmantel los, das dies erklären könnte?

Zur Bestätigung ist der Zugriff auf diesen neuen Singleton die einzige Änderung, und ich kann dies zuverlässig wiederherstellen, indem ich den Aufruf des Caches auskommentiere.

JoeGeeky
quelle
8
Haben Sie einen Profiler auf den Code hingewiesen?
Timo Geusch
2
Die Profilerstellung wird diese Frage wahrscheinlich nur beantworten, wenn Sie die CLR und möglicherweise den Windows-Kernel profilieren (keine leichte Aufgabe für den durchschnittlichen Programmierer).
Igby Largeman
1
@ JoeGeeky Also gut, ich denke, das Einzige, was ich für mich tun kann, ist +1 und Bevorzugung! Es scheint seltsam, da sie doch beide auf der gleichen Ebene der Indirektion sind und sowieso in den Prozessor-Cache passen sollten, etc ...
Max
2
FWIT Ich habe ein paar Threads erzeugt und einige Timer ausgeführt. Ich habe eine Klasse instanziiert, singleton, lockedSingleton und dict <string, string>. Nach der ersten Instanziierung von jedem dauerten aufeinanderfolgende Läufe für ein bestimmtes Objekt etwa 2000 ns. Das Wörterbuch lief 2x langsamer, kann durch Konstruktorcode verursacht werden ... es ist langsamer als das Sperren von selbst. In Anbetracht des gesamten GC, der Handhabung des Betriebssystems für Thread-Warteschlangen und anderen Overheads, kann man diese Frage nicht wirklich beantworten. Aufgrund meiner Ergebnisse glaube ich jedoch nicht, dass das Problem mit Singletons zu tun hat. Nicht, wenn es wie auf MSDN implementiert ist. Schließt Compiler-Optimierungen aus.
P. Brian Mackey
1
@JoeGeeky - ein weiterer Gedanke: Fügt die Verwendung des Caches eine Indirektionsebene hinzu? Bei häufigem Zugriff kann das Herunterschalten eines zusätzlichen Zeiger-Deref (oder MSIL-Äquiv.) Über eine lokale, weniger indirekte Kopie einige Zeit in Anspruch nehmen.
sdg

Antworten:

8

Es könnte sein, dass der unveränderliche Zustand eine Cache-Zeile mit etwas Veränderlichem teilt. In diesem Fall kann eine Änderung des nahegelegenen veränderlichen Zustands dazu führen, dass diese Cache-Zeile über Kerne hinweg erneut synchronisiert wird, was die Leistung beeinträchtigen kann.

Aidan Cully
quelle
3
Das klingt nach einem false sharingSzenario, das Sie beschreiben. Um das zu isolieren, muss ich L2 Cache profilieren. Leider handelt es sich hierbei um Referenztypen, sodass das Hinzufügen von Pufferplatz keine Option ist, wenn dies tatsächlich der Fall ist.
JoeGeeky
3

Ich würde sicherstellen, dass die Equals()und GetHashCode()Methoden der Objekte, die Sie als Schlüssel für das Wörterbuch verwenden, keine unerwarteten nicht-threading-freundlichen Nebenwirkungen haben. Profiling würde hier sehr helfen.

Wenn es sich bei Ihren Schlüsseln zufällig um Zeichenfolgen handelt, dann haben Sie es vielleicht schon: Es geht das Gerücht, dass Zeichenfolgen sich wie unveränderliche Objekte verhalten, aber aus Gründen bestimmter Optimierungen intern auf veränderbare Weise implementiert sind, mit allem, was dies in Bezug auf Multithreading mit sich bringt .

Ich würde versuchen, das Wörterbuch an die Threads zu übergeben, die es als regulären Verweis anstelle eines Singletons verwenden, um festzustellen, ob das Problem in der Gemeinsamkeit oder in der Singletonität des Wörterbuchs liegt. (Beseitigung der möglichen Ursachen.)

Ich würde auch versuchen, mit einem ConcurrentDictionaryanstelle eines regulären Dictionarynur für den Fall, dass seine Verwendung einige überraschende Ergebnisse liefert. Es gibt viel zu spekulieren über das vorliegende Problem, wenn ConcurrentDictionarysich herausstellt, dass eine viel bessere oder viel schlechtere Leistung als Ihre normale erbringt Dictionary.

Wenn keiner der oben genannten Punkte auf das Problem hinweist, dann würde ich vermuten, dass die verschlechterte Leistung durch eine seltsame Art von Konflikt zwischen dem müllsammelnden Thread und dem Rest Ihrer Threads verursacht wird, während der Müllsammler versucht, herauszufinden, ob Die Objekte in Ihrem Wörterbuch müssen entsorgt werden oder nicht, während Ihre Threads auf sie zugreifen.

Mike Nakis
quelle