Warum ist lokaler Thread-Speicher so langsam?
Arbeite ich an einem custom-mark-release-Stil-memory-allocator für die D programming language, das funktioniert durch die Zuordnung von thread-lokalen Regionen. Es scheint, dass der thread local storage Engpass verursacht eine riesige (~50%) Verlangsamung bei der Zuweisung von Speicher, die aus diesen Regionen im Vergleich zu einer ansonsten identischen single-threaded-version der code, auch nach der Gestaltung mein code, nur eine TLS-lookup pro allocation/deallocation. Dies basiert auf der Zuweisung/Freigabe von Speicher eine große Anzahl von Zeiten in einer Schleife, und ich versuche, herauszufinden, ob es ein Artefakt meiner benchmarking-Methode. Mein Verständnis ist, dass thread local storage sollte im Grunde nur Zugriff auf etwas, was durch eine zusätzliche Schicht der Dereferenzierung, ähnlich dem Zugriff auf eine variable über einen Zeiger. Ist das falsch? Wie viel overhead macht der thread-lokalen Speicher in der Regel haben?
Hinweis: Obwohl ich erwähnen, D, ich interessiere mich auch für die Allgemeinen Antworten, die nicht spezifisch für D, da D die Implementierung der thread-lokaler Speicher wird sich wahrscheinlich verbessern, wenn es langsamer ist, als die besten Implementierungen.
InformationsquelleAutor der Frage dsimcha | 2009-02-03
Du musst angemeldet sein, um einen Kommentar abzugeben.
Die Geschwindigkeit hängt von der TLS-Implementierung.
Ja, Sie sind richtig, dass TLS kann so schnell wie ein pointer-Suche. Es kann sogar sein, schneller auf Systemen mit einer memory management unit.
Für die pointer-Suche Sie Hilfe brauchen, aus dem scheduler aber. Der Planer muss - auf ein task-switch - update der Zeiger auf der TLS-Daten.
Ein weiterer schneller Weg für die Implementierung von TLS über die Memory Management Unit. Hier die TLS wird behandelt wie alle anderen Daten, mit der Ausnahme, dass TLS-Variablen zugeordnet sind, in einem speziellen segment. Der scheduler - task-switch - Karte den richtigen chunk-Speicher in den Adressraum der task.
Wenn der scheduler nicht unterstützt jede dieser Methoden, der compiler/library zu tun hat, die folgenden:
Offensichtlich tut dies alles für jeden TLS-Daten zugreifen, dauert eine Weile und kann bis zu drei OS-Aufrufe: Immer die ThreadId, Nehmen und lassen Sie die semaphore.
Semaphore ist btw erforderlich, um sicherzustellen, dass kein thread liest von der TLS-Zeiger Liste, während ein anderer thread in der Mitte und erzeugt einen neuen thread. (und als solche weisen Sie eine neue TLS-block, und ändern Sie die datastructure).
Leider ist es nicht ungewöhnlich, um zu sehen, die langsam TLS-Implementierung in der Praxis.
InformationsquelleAutor der Antwort Nils Pipenbrinck
Thread einheimischen in D sind wirklich schnell. Hier sind meine tests.
64-bit-Ubuntu, core i5, dmd v2.052
Compiler-Optionen: dmd -O -release -inline -m64
So verlieren wir nur 1,2 Sekunden einer der CPU-Kerne pro 1000*1000*1000 thread-lokale Zugriffe.
Thread einheimischen sind aufgerufen mit %fs registrieren - es gibt also nur ein paar Prozessor-Befehle eingebunden:
Demontage mit objdump -d:
Vielleicht compiler könnte sogar noch schlauer und cache-thread-lokalen vor der Schleife ein register
und zurück zum thread-lokalen am Ende (es ist interessant zu vergleichen, mit gdc compiler),
aber auch jetzt noch Fragen sind sehr gut IMHO.
InformationsquelleAutor der Antwort Andriy
Muss man sehr vorsichtig sein bei der Interpretation der benchmark-Ergebnisse. Zum Beispiel ein den letzten thread in der D-newsgroups geschlossen, aus einem benchmark, dass die dmd die code-Generierung wurde, was zu einer größeren Verlangsamung in einer Schleife, die haben das rechnen, aber in der Aktualität die Zeit war geprägt von der Laufzeit Hilfsfunktion, habe lange division. Der compiler code generation nichts zu tun hatte mit der Verlangsamung.
Zu sehen, welche Art von code generiert wird, für tls, kompilieren und obj2asm diesem code:
TLS ist sehr unterschiedlich ausgeführt auf Windows als auf Linux, und wird sehr unterschiedlich sein, wieder auf OSX. Aber, in allen Fällen, es werden noch viele weitere Anweisungen, als ein einfaches laden einer statischen Speicherbereich. TLS ist immer zu langsam, relativ einfachen Zugang. Zugriff auf TLS-globals in einer engen Schleife geht zu langsam, zu. Versuchen Sie das Zwischenspeichern der TLS-Wert in eine temporäre statt.
Schrieb ich einige thread-pool-Zuordnung code-Jahren und in den Cache der TLS-handle auf den pool, das hat gut funktioniert.
InformationsquelleAutor der Antwort Walter Bright
Wenn Sie nicht verwenden können compiler die Unterstützung für TLS, die Sie verwalten können, TLS selbst.
Ich baute eine wrapper-Vorlage für C++, so ist es leicht zu ersetzen Sie eine zugrunde liegende Implementierung.
In diesem Beispiel habe ich es umgesetzt für Win32.
Hinweis: Da Sie nicht erhalten, eine unbegrenzte Anzahl von TLS-Indizes je Prozess (zumindest unter Win32),
Sie sollten zeigen Sie auf heap-Blöcke groß genug sind, um alle thread-spezifischen Daten.
Auf diese Weise haben Sie eine minimale Anzahl von TLS Indizes und Verwandte Abfragen.
Im "besten Fall", Sie hätte nur 1 TLS-Zeiger zeigt auf eine private heap-block pro thread.
Kurz gesagt: zeigen Sie nicht auf einzelne Objekte, anstelle Punkt-zu-thread bestimmte, heap-Speicher/Behältnissen mit einem Inhalt von Objekt Zeiger auf eine bessere Leistung erzielen.
Nicht vergessen, Speicher freizugeben, wenn es nicht wieder verwendet.
Ich Tue dies, indem er einen thread in einer Klasse (wie Java funktioniert) und mit TLS von Konstruktor und Destruktor.
Desweiteren habe ich das speichern Häufig benutzter Daten wie der thread-handles und ID ' s als Schüler.
Verwendung:
InformationsquelleAutor der Antwort sam
Habe ich entworfen, multi-Tasker für eingebettete Systeme und konzeptionell die Voraussetzung für thread-local storage ist, dass der Kontext-switch-Methode save/restore ein Zeiger auf thread-lokalen Speicher zusammen mit der CPU-Register und was auch immer sonst es ist, speichern/wiederherstellen. Für embedded-Systeme, die immer mit der gleichen code-wenn Sie einmal begonnen haben, ist es am einfachsten, einfach speichern/wiederherstellen-Zeiger, die Punkte zu einem festen block format für jeden thread. Nett, sauber, einfach und effizient.
Solcher Ansatz funktioniert gut, wenn man nicht dagegen haben, dass Platz für alle thread-lokalen variable zugeordnet, in jedem thread-auch diejenigen, die nie wirklich zu nutzen-und wenn alles, was, die gehen, um innerhalb der lokalen thread-Speicher-Blocks kann definiert werden als eine einzelne Struktur. In diesem Szenario erhalten Sie Zugriff auf thread-lokale Variablen können fast so schnell wie der Zugriff auf andere Variablen, der einzige Unterschied wird ein extra-Zeiger zu dereferenzieren. Leider sind viele PC-Anwendungen erfordern etwas komplizierter.
Auf einige Rahmenbedingungen für den PC, ein thread wird nur Speicherplatz für thread-statische Variablen, wenn ein Modul verwendet Variablen, die ausgeführt wurde, auf diesem thread. Das kann zwar manchmal von Vorteil sein, es bedeutet, dass verschiedene threads haben oft Ihre lokalen Speicher anders angeordnet. Folglich kann es notwendig sein, die Fäden, um irgendeine Art von durchsuchbaren index, wo Ihre Variablen befinden, und leiten Sie alle Zugriffe auf diese Variablen durch index.
Ich würde erwarten, dass, wenn der Rahmen weist eine kleine Menge von festen-format Speicher, ist es möglicherweise hilfreich, um einen cache mit den letzten 1-3 thread-lokalen Variablen zugegriffen, da in vielen Szenarien sogar ein einzelnes Element-cache könnten, bieten eine ziemlich hohe Trefferquote.
InformationsquelleAutor der Antwort supercat
Haben wir gesehen, ähnliche performance-Probleme von TLS (auf Windows). Wir verlassen uns auf Sie für bestimmte kritische Vorgänge im inneren unseres Produkts "kernel'. Nach einiger Mühe habe ich beschlossen, zu versuchen und zu verbessern.
Ich bin froh sagen zu können, dass wir jetzt eine kleine API, Angebote > 50% Reduktion der CPU-Zeit für einen gleichwertigen Betrieb, wenn die callin-thread nicht "wissen", seine thread-id und > 65% zu reduzieren, wenn aufrufenden thread bereits erhalten hat, seine thread-id (vielleicht bei einigen anderen früheren Schritt der Verarbeitung).
Die neue Funktion ( get_thread_private_ptr() ) gibt immer einen Zeiger auf eine Struktur, die wir intern verwenden, um zu halten alle Arten, so brauchen wir nur eine pro thread.
Alles in allem denke ich, dass die Win32-TLS support ist wirklich schlecht gestaltete.
InformationsquelleAutor der Antwort Hugh