Sollten ineinandergreifende Implementierungen, die auf CompareExchange basieren, SpinWait verwenden?

8

Nachfolgend finden Sie eine Implementierung einer ineinandergreifenden Methode, die auf basiert Interlocked.CompareExchange.

Ist es ratsam, dass dieser Code SpinWaitvor dem Wiederholen einen Spin verwendet?

public static bool AddIfLessThan(ref int location, int value, int comparison)
{
    int currentValue;
    do
    {
        currentValue = location; // Read the current value
        if (currentValue >= comparison) return false; // If "less than comparison" is NOT satisfied, return false
    }
    // Set to currentValue+value, iff still on currentValue; reiterate if not assigned
    while (Interlocked.CompareExchange(ref location, currentValue + value, currentValue) != currentValue);
    return true; // Assigned, so return true
}

Ich habe SpinWaitin diesem Szenario verwendet gesehen, aber meine Theorie ist, dass es unnötig sein sollte. Immerhin enthält die Schleife nur eine Handvoll Anweisungen, und es gibt immer einen Thread, der Fortschritte macht.

Angenommen, zwei Threads rennen um diese Methode, und der erste Thread ist sofort erfolgreich, während der zweite Thread zunächst keine Änderung vornimmt und wiederholt werden muss. Ist es ohne andere Konkurrenten überhaupt möglich, dass der zweite Thread beim zweiten Versuch fehlschlägt ?

Wenn der zweite Thread des Beispiels beim zweiten Versuch nicht fehlschlagen kann, was können wir dann mit einem gewinnen SpinWait? In dem unwahrscheinlichen Fall, dass hundert Fäden um die Durchführung der Methode rennen, einige Zyklen abschneiden?

Timo
quelle
3
@MindSwipe OP fragt nicht, ob Interlocked verwendet werden muss. Sollte er SpinWait anstelle des leeren Schleifenkörpers verwenden?
Matthew Watson
1
Sie sollten wahrscheinlich nur verwenden SpinOnce, um zu verhindern, dass ein Single-Thread-Betriebssystem möglicherweise ausgehungert wird. Siehe stackoverflow.com/questions/37799381/…
Matthew Watson
1
@MindSwipe Die Racebedingung wird aufgrund der Verwendung bereits korrekt behandelt Interlocked. Zur Verdeutlichung interessiert mich nur, ob a SpinWaitsinnvoll ist oder nicht , um beispielsweise CPU-Zyklen sinnvoll zu speichern oder (danke @MatthewWatson!) Zu verhindern, dass ein Single-Thread-Betriebssystem ausgehungert wird.
Timo
1
@AloisKraus Ich verstehe das theoretisch, aber meine Argumentation ist, dass diese Schleife beim nächsten Versuch erfolgreich ist , unabhängig davon, ob wir warten oder nicht. Deshalb erwarte ich, dass dies SpinWaitnicht einmal den Stromverbrauch senkt, da ohnehin die gleiche Anzahl von Versuchen durchgeführt wird! (Mit zwei Threads ist das ein Versuch für den ersten und zwei für den zweiten Thread.)
Timo
1
@ Timo, ich denke, Sie können von einer Spin-Sperre auf Plattformen profitieren, auf denen Interlocked nicht von der Hardware unterstützt wird, sondern emuliert werden muss.
Nick

Antworten:

2

Meine nicht-Expertenmeinung ist, dass in diesem speziellen Fall, in dem gelegentlich zwei Threads aufgerufen werden AddIfLessThan, a SpinWaitnicht benötigt wird. AddIfLessThanDies könnte von Vorteil sein, wenn beide Threads in einer engen Schleife aufgerufen werden, sodass jeder Thread einige μs lang ohne Unterbrechung Fortschritte machen kann.

Eigentlich habe ich ein Experiment durchgeführt und die Leistung eines Threads gemessen, der AddIfLessThanin einer engen Schleife aufgerufen wird , im Vergleich zu zwei Threads. Die beiden Threads benötigen fast viermal mehr, um die gleiche Anzahl von Schleifen (kumulativ) zu erstellen. Durch Hinzufügen von a SpinWaitzur Mischung werden die beiden Threads nur geringfügig langsamer als der einzelne Thread.

Theodor Zoulias
quelle
1
Welchen Effekt hat SpinWait auf einen Thread, z. B. gibt es einen Grund, ihn nicht zu verwenden?
Ian Ringrose
2
@ IanRingrose macht es 15% langsamer. Ich deklariere eine Variable vom Typ SpinWaitinnerhalb der AddIfLessThanMethode, und obwohl es sich um einen Werttyp handelt und seine SpinOnceMethode nie aufgerufen wird, fügt sie dennoch etwas Overhead hinzu.
Theodor Zoulias
3
@TheodorZoulias: Sie könnten in Ihrem IL-Körper erklären, Init von Einheimischen zu überspringen, was Ihrem fast die Perfektion zurückgeben sollte. Sie müssen den IL-Code dafür ausgeben, da es noch keine Sprachunterstützung dafür gibt. Siehe github.com/dotnet/csharplang/blob/master/proposals/…. Wenn Sie einmal drehen möchten, können Sie Reset und dann SpinOnce aufrufen.
Alois Kraus
1
Danke @TheodorZoulias! Zu Ihrer Information, die beiden Themen waren nur ein Beispiel, um meinen Standpunkt zu veranschaulichen. Dies ist eine Klassenbibliothek und kann nach Belieben des Verbrauchers verwendet werden. Das Beste, was wir tun können, ist, Annahmen über die Wahrscheinlichkeit von Rennbedingungen mit vielen Threads zu treffen. Angesichts der geringen Zeit, die die Operation benötigt, halte ich es für unwahrscheinlich, dass viele Threads diese Operation überlappen.
Timo
1
Wenn Sie eine enge Schleife aktualisieren, sollten Sie wahrscheinlich in Betracht ziehen, den Wert privat zu machen und am Ende der Schleife einmal zu aktualisieren. Wenn der gemeinsame Wert mehr oder weniger aktuell bleiben muss und die Schleife extrem lang ist, sollten Sie den Wert aktualisieren, während Sie einen Fortschrittsbalken einmal alle X ms vorrücken.
Neugieriger
2

Zwei Themen sind einfach kein SpinWaitDiskussionsthema. Dieser Code sagt uns jedoch nicht, wie viele Threads tatsächlich um die Ressource konkurrieren können, und bei relativ hoher Anzahl von Threads hat die Verwendung vonSpinWait vorteilhaft sein. Insbesondere bei einer höheren Anzahl von Threads wird die virtuelle Warteschlange von Threads, die versuchen, die Ressource erfolgreich zu erfassen, länger, und diejenigen Threads, die am Ende zufällig bedient werden, haben gute Chancen, ihre vom Scheduler zugewiesene Zeitscheibe zu überschreiten, was wiederum möglich ist führen zu einem höheren CPU-Verbrauch und können die Ausführung anderer geplanter Threads auch mit höherer Priorität beeinträchtigen. DasSpinWaithat eine gute Antwort auf diese Situation, indem eine Obergrenze für zulässige Drehungen festgelegt wird, nach der die Kontextumschaltung ausgeführt wird. Es ist also ein vernünftiger Kompromiss zwischen der Notwendigkeit, einen teuren Systemaufruf durchzuführen, um eine Kontextumschaltung auszulösen, und dem unkontrollierten CPU-Verbrauch im Benutzermodus, der in bestimmten Situationen die Ausführung anderer Threads beeinträchtigen kann.

Dmytro Mukalov
quelle
Die beiden Themen waren nur ein Beispiel, um meinen Standpunkt zu veranschaulichen. Dies ist eine Klassenbibliothek. Wir sollten in der Lage sein, jedes realistische Szenario unter Berücksichtigung seiner Wahrscheinlichkeit zu bewältigen. Angesichts der geringen Zeit, die die Operation benötigt, halte ich es für unwahrscheinlich, dass viele Threads diese Operation überlappen.
Timo
Sie machen einen fairen Punkt. Ich bin damit einverstanden, dass bei vielen Threads einige Threads viele fehlschlagen würden, bevor sie erfolgreich sind. Ein Teil von mir ist jedoch der Meinung, dass die Operation so klein ist, dass beispielsweise 100-malige Ausführung immer noch Erdnüsse sind. Würden Sie in diesem Sinne erwarten, dass es den SpinWaitOverkill macht, oder nicht?
Timo
2
@ Timo, die einzige Antwort ist die Messung, insbesondere wenn man bedenkt, dass dies ein Bibliothekscode sein wird, der in der Lage sein sollte, mit verschiedenen von mir angenommenen Last- und Hardwareszenarien umzugehen. Für eine bessere Fairness und damit für einen besseren Durchsatz SpinWaitist die bessere Strategie (mit relativ geringen Kosten von Dutzenden von Mikrosekunden) als die Nichtanwendung, es sei denn, Sie schließen im Voraus die Möglichkeit von Szenarien mit hoher niedriger Last aus, um einen Lastpunkt zu finden, an dem der Durchsatz möglich ist Betroffen zu sein würde Ihnen einen Hinweis geben, ob Sie es brauchen oder nicht (beachten Sie, dass es auch von der Anzahl der Kerne abhängen kann).
Dmytro Mukalov
Bei so vielen Threads, die eine gemeinsame Ressource so lange benötigen, dass sie häufig ihre Zeitscheibe verbrauchen, würde ich entweder in Betracht ziehen, private Kopien der Ressource zu erstellen (um später synchronisiert zu werden) oder sogar Threads aufzugeben.
Neugieriger
@curiousguy Wie bereits in 4 Kommentaren erwähnt, handelt es sich um eine Klassenbibliothek. Natürlich die konsumierende Code einen fallspezifischen Ansatz verwenden. Der Zweck der Klassenbibliothek besteht jedoch darin, für verschiedene Anwendungsfälle stabil zu sein. Deshalb bleibt die Frage, in welchen Situationen der SpinWaitMehrwert entsteht. Bisher scheinen dies (A) der Single-Core-Fall und (B) möglicherweise der sehr umstrittene Fall zu sein.
Timo