TL;DR: Macht es jemals Sinn, in einer Producer-Consumer-Warteschlange einen unnötigen (aus Sicht des C++-Speichermodells) Speicherzaun oder eine unnötig starke Speicherreihenfolge zu platzieren, um eine bessere Latenzzeit auf Kosten eines möglicherweise schlechteren Durchsatzes zu erzielen?
Das C++-Speichermodell wird auf der Hardware ausgeführt, indem eine Art Speicherzäune für stärkere Speicherordnungen vorhanden sind und sie nicht für schwächere Speicherordnungen vorhanden sind.
Insbesondere, wenn der Produzent dies tut store(memory_order_release)
und Verbraucher beobachtet den gespeicherten Wert mit load(memory_order_acquire)
, gibt es keine Zäune zwischen Ladung und Lager. Auf x86 gibt es überhaupt keine Zäune, auf ARM werden Zäune vor dem Speichern und nach dem Laden ausgeführt.
Der ohne Zaun gespeicherte Wert wird eventuell durch Laden ohne Zaun eingehalten (evtl. nach einigen erfolglosen Versuchen)
Ich frage mich, ob das Anbringen eines Zauns auf beiden Seiten der Warteschlange den zu beobachtenden Wert beschleunigen kann? Wie hoch ist die Latenz mit und ohne Zaun, wenn ja?
Ich erwarte, dass nur eine Schleife mit load(memory_order_acquire)
und pause
/ yield
Die Beschränkung auf Tausende von Iterationen ist die beste Option, da sie überall verwendet wird, aber Sie möchten verstehen, warum.
Da es bei dieser Frage um das Hardwareverhalten geht, gehe ich davon aus, dass es keine allgemeine Antwort gibt. Wenn ja, wundere ich mich hauptsächlich über x86 (x64-Geschmack) und in zweiter Linie über ARM.
Beispiel:
T queue[MAX_SIZE]
std::atomic<std::size_t> shared_producer_index;
void producer()
{
std::size_t private_producer_index = 0;
for(;;)
{
private_producer_index++; // Handling rollover and queue full omitted
/* fill data */;
shared_producer_index.store(
private_producer_index, std::memory_order_release);
// Maybe barrier here or stronger order above?
}
}
void consumer()
{
std::size_t private_consumer_index = 0;
for(;;)
{
std::size_t observed_producer_index = shared_producer_index.load(
std::memory_order_acquire);
while (private_consumer_index == observed_producer_index)
{
// Maybe barrier here or stronger order below?
_mm_pause();
observed_producer_index= shared_producer_index.load(
std::memory_order_acquire);
// Switching from busy wait to kernel wait after some iterations omitted
}
/* consume as much data as index difference specifies */;
private_consumer_index = observed_producer_index;
}
}
Die Bereitstellung eines Codebeispiels wäre hier hilfreich. Ich bin mir nicht ganz sicher, was Sie fragen
– Bartop
4. Mai 2020 um 11:41 Uhr
Ich habe ein Beispiel gegeben, obwohl die Frage beantwortet ist
– Alex Gutenjew
4. Mai 2020 um 12:54 Uhr
@bartop: Nur meine 2 Cent: Es schien mir ohne Beispiel klar zu sein. Es könnte einer dieser Fälle sein, in denen es für Leute, die die Antwort kennen, aus der Frage klar ist. Es ist wahrscheinlich keine schlechte Sache, eine zu haben, vielleicht hilft es mehr Lesern, den Sinn meiner Antwort zu verstehen. (Es geht um den Versuch, die Latenz zwischen den Kernen zu minimieren.)
– Peter Cordes
4. Mai 2020 um 13:19 Uhr