Forum: PC-Programmierung C++ / periodischer Background Thread: Alternativen


von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

eine Frage aus Neugier, im Rahmen meines Programmes „Timm lernt modernes 
C++".

Wenn man Objekte gern nicht im Haupt-Thread freigeben möchte, sondern 
das an einen dedizierten Hintergrund-Thread delegieren möchte, gibt es 
die Variante, die Objekte einfach per shared_ptr zu verwalten und einen 
Hintergrund-Thread periodisch prüfen zu lassen, ob die Reference-Counts 
eine Vernichtung nahelegen, die Objekte hängen dabei in einem Container, 
so dass der Count nicht 0 wird :-).

In diesem kurzen Tutorial ist das zum Beispiel so gemacht:
https://www.juce.com/doc/tutorial_looping_audio_sample_buffer_advanced

Das Tutorial ist allerdings sehr alt und stammt aus einer Zeit, als es 
noch keine shared_ptr gab, darum verwendet es selbstgestrickte Objekte: 
ReferenceCountedObjectPtr. Das ist aber Jacke wie Hose, denke ich.

Meine Frage:
Gibt es mit modernem C++ (11, 14 oder 17) ähnlich leistungsstarke 
naheliegende Alternativen?

Mit leistungsstark meine ich, dass bei diesem Prinzip der 
Vordergrund-Thread nicht blockieren kann und der Hintergrund-Thread 
früher oder später jedes alte Objekt vernichtet, selbst wenn mal ein 
Objekt so groß ist, dass das lange dauert, aber selbst dadurch würde die 
Auftragsannahme nicht blockiert.

Ich frage aus zwei Gründen: Erstens, wenn es eine naheliegende 
Vorgehensweise gibt, wäre das mit ziemlicher Sicherheit etwas, das ich 
wissen will / muss und zweitens verbringt der Hintergrund Thread im 
typischen Fall 99,99999% seines Lebens mit fruchtlosen Prüfungen. 
Effektiv aber vlt. nicht effizient?

Über jeden sachdienlichen Hinweis würde ich mich sehr freuen!

Herzliche Grüße und vielen Dank

 Timm

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Du könntest für deinen shared_ptr einen Custom Deleter nutzen, welcher 
den Speicher nicht direkt freigibt, sondern auf die Liste des 
freizugebenden Speichers setzt. Diese Liste wird dann durch deinen 
Hintergrundthread abgearbeitet. Damit der Hintergrundthread nicht mit 
sinnlosem Polling belastet wird kann man eine Condition Variable zur 
Benachrichtigung nutzen.

Ob das irgendwelche Geschwindigkeitsvorteile gegenüber einem normalem 
delete ergibt müsste man dann testen.

: Bearbeitet durch User
von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo Sebastian,

Sebastian V. schrieb:
> Du könntest für deinen shared_ptr einen Custom Deleter nutzen, welcher
> den Speicher nicht direkt freigibt, sondern auf die Liste des
> freizugebenden Speichers setzt. Diese Liste wird dann durch deinen
> Hintergrundthread abgearbeitet. Damit der Hintergrundthread nicht mit
> sinnlosem Polling belastet wird kann man eine Condition Variable zur
> Benachrichtigung nutzen.

super, danke. Das Problem, dachte ich, wäre, dass, wenn ich notify 
benutze, der Hintergrundthread aber noch nicht wartet, dann würde die 
Nachricht im Nirvana landen und der Speicher nicht freigegeben werden. 
Da ich an einige GB große Blöcke denke, wäre das schon tendenziell nicht 
so schön.

Oder ist das nicht so?

> Ob das irgendwelche Geschwindigkeitsvorteile gegenüber einem normalem
> delete ergibt müsste man dann testen.

darum geht es mir eher nicht. Es geht nur darum, die Klaviatur kennen zu 
lernen. Aber die Deallokation ist davon ab auch gigantisch langsam.

vlg und besten Dank schon mal!

Timm

von Mikro 7. (mikro77)


Lesenswert?

> super, danke. Das Problem, dachte ich, wäre, dass, wenn ich notify
> benutze, der Hintergrundthread aber noch nicht wartet, dann würde die
> Nachricht im Nirvana landen und der Speicher nicht freigegeben werden.

Es muss schon eine Schnittstelle bereitgestellt werden; bspw. eine 
Queue. Für jedes Löschen wird das zu löschende Objekt in die Queue 
eingestellt und der Löschthread wird informiert (Condition). Also 
klassisches Producer / Consumer Szenario.

Der Custom Deleter ist dann ein Wrapper der die Löschfunktionm (enqueue) 
aufruft.

Interessant wird das Konzept wenn das Freigeben von Objekten 
ressourcenintensiv ist, der Hauptthread kurze Reaktionszeiten aufweisen 
muss, und mehrere Cores vorhanden sind.

von Sebastian V. (sebi_s)


Lesenswert?

Timm R. schrieb:
> Das Problem, dachte ich, wäre, dass, wenn ich notify
> benutze, der Hintergrundthread aber noch nicht wartet, dann würde die
> Nachricht im Nirvana landen und der Speicher nicht freigegeben werden.

Das kann so zwar passieren, aber die Nachricht enthält ja nicht die 
freizugebenden Daten, sondern nur die Info, dass neue Daten zum 
freigeben in die Liste eingetragen wurden. Die Liste selbst muss dann 
mit einem Lock geschützt werden, damit nicht beide Threads gleichzeitig 
darauf zugreifen.

Bevor der Hintergrund Thread sich dann wirklich schlafen legt schaut er 
nochmal in die Liste und nur wenn die leer ist legt er sich schlafen. 
Diese Aktion ist atomar und daher kann sich zwischen Prüfung auf leere 
Liste und Schlafen legen die Liste sich nicht verändert haben. Wenn der 
Hintergrund Thread also ein notify verpasst, dann wird er das schon an 
der nicht leeren Liste merken.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

ahhh! Jetzt habe ichs, danke Sebastian, ich glaube, so müsste es gehen. 
Mal ein Prog schreiben, wenn ich ein Beispiel habe, poste ich den Code 
hier, zwecks Stellungnahme.

vlg und Danke!

 Timm

von Sebastian V. (sebi_s)


Lesenswert?

Hab mal ein schnelles Beispiel zusammen gebastelt:
1
#include <iostream>
2
#include <thread>
3
#include <condition_variable>
4
#include <queue>
5
6
using namespace std::chrono_literals;
7
8
std::mutex mutex;
9
std::condition_variable cv;
10
11
std::queue<int> queue;
12
13
void workerThread()
14
{
15
    while(1) {
16
        int value;
17
        { std::unique_lock<std::mutex> lock(mutex);
18
            // Wait for non empty queue
19
            cv.wait(lock, []{ return !queue.empty(); });
20
21
            // Get first value from queue
22
            value = queue.front();
23
            queue.pop();
24
        }
25
26
        // Process value without holding the lock
27
        std::cout << value << std::endl;
28
    }
29
}
30
31
void addElement(int value)
32
{
33
    std::lock_guard<std::mutex> lock(mutex);
34
    queue.push(value);
35
    cv.notify_all();
36
}
37
38
int main()
39
{
40
    std::thread t(workerThread);
41
    while(1) {
42
        addElement(rand());
43
        std::this_thread::sleep_for(500ms);
44
    }
45
}

Der interessanteste Teil ist wohl das cv.wait(...). Hier habe ich die 
Variante mit Lambda Funktion benutzt, die man nicht falsch benutzen 
kann. Die Zeile hat den gleichen Effekt wie:
1
while(queue.empty())
2
    cv.wait(lock);
Also hier ist es wichtig erst auf den Wert zu testen und nur dann zu 
warten wenn der Wert nicht dem entspricht auf das man wartet. Falsch 
wäre also sowas:
1
// This is WRONG!!!
2
do {
3
    cv.wait(lock);
4
} while(queue.empty());

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Das ganze ist ne ziemlich komplizierte Angelegenheit und ich würde 
Prüfen, ob eine derartige Methode nötig ist, bevor ich ernsthaft darüber 
nachdenke wie ich es umsetze. Aber mein erster Gedanke ist relativ 
einfach.
Zuerst den Buffer mit make_unique erzeugen. Und den unique_ptr in den 
anderen Thread std::moven, wenn der Buffer nicht mehr gebraucht wird. 
Alternativ kann man einen Custom Deleter benutzen, wie Sebastian V. O. 
(sebi_s) für shared_ptr angemerkt hat. Ich würde aber nicht ohne Grund 
(shared ownership) einen shared_ptr benutzen.

Ich habe mir das Beispiel auf der JUCE Seite kurz angeguckt und etwas 
durch die Doku geklickt. Dabei liegt die Betonung auf "kurz" und 
"etwas", es handelt sich also um gefährliches Halbwissen. Was mir dabei 
aufgefallen ist:

- Es gibt keine Profiling Daten, die zeigen, dass diese Lösung schneller 
ist als ein Destruktor im Main Thread, oder dass dieser Destruktor 
überhaupt ein Bottleneck ist.

- Der erste Satz ist:
> This tutorial shows how to play and loop audio stored in an
> AudioSampleBuffer object using thread-safe techniques.

- in checkForBuffersToFree
1
if (buffer->getReferenceCount() == 2)                          // [3]
2
            buffers.remove (i);
Hier wird weder garantiert, dass das der Destruktor der entsprechende 
Instanz sofort aufgerufen wird, noch dass es jemals in diesem Thread 
passieren wird. Zwischen dem Vergleich und dem remove kann in dem 
anderen Thread alles mögliche passieren, da die Syncronisation fehlt. 
Unter anderem kann der ReferenceCount inkrementiert werden, womit dieser 
Thread bei remove jedes Wissen über die Instanz verliert.

- Aus der Doku zu
1
template<class ObjectClass, class TypeOfCriticalSectionToUse = DummyCriticalSection>
2
class ReferenceCountedArray;
> To make all the array's methods thread-safe, pass in "CriticalSection" as
> the templated TypeOfCriticalSectionToUse parameter, instead of the default
> DummyCriticalSection.
Benutzt wird
1
ReferenceCountedArray<ReferenceCountedBuffer> buffers;
in beiden Threads...

Ich sehe nicht, wie das Versprechen im ersten Satz eingehalten werden 
kann. Und auch wenn man das Beispiel mit std::mutex oder std::atomic 
threadsicher machen würde, glaube ich nicht, dass man irgendeinen 
Vorteil hat. Ich lehne mich mal aus dem Fenster und behaupte sogar, dass 
der Rechenaufwand und die Latenzzeiten größer werden. Immerhin muss man 
in regelmäßigen Abständen alle Zugriffe auf die Buffer blockieren, damit 
der sicher Hintergrundthread das ganze Array durchgehen und aufrümen 
kann und das aufräumen ist ja angeblich sehr teuer.

von Mikro 7. (mikro77)


Lesenswert?

Im Rahmen des Programms „Timm lernt modernes C++" ...

...vor dem notify() sollte ein unlock durchgeführt werden, falls 
Performance eine Rolle spielt. Anderenfalls läuft der Consumer 
möglicherweise sofort auf den Lock.

...Mutexe sind (relativ) teuer. Falls das eine Rolle spielt, sind 
"Lockless" Implementierungen eine Alternative. Hier ein Beispiel: 
http://www.boost.org/doc/libs/1_53_0/doc/html/atomic/usage_examples.html#boost_atomic.usage_examples.example_ringbuffer

Btw: ich bevorzuge
1
while(queue.empty())
2
    cv.wait(lock);
statt der Lambda Alternative da imho besser lesbar.

von Sebastian V. (sebi_s)


Lesenswert?

Mikro 7. schrieb:
> ...vor dem notify() sollte ein unlock durchgeführt werden, falls
> Performance eine Rolle spielt. Anderenfalls läuft der Consumer
> möglicherweise sofort auf den Lock.

Das habe ich jetzt mal weggelassen um den Beispiel Code möglichst kurz 
zu halten. Mich würde aber mal interssieren ob du dazu irgendwelche 
Performance Vergleiche hast. Ich könnte mir vorstellen, dass sich da 
überhaupt nur unter recht speziellen Umständen überhaupt ein Unterschied 
messen lässt.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo mh,

mh schrieb:

> - Es gibt keine Profiling Daten, die zeigen, dass diese Lösung schneller
> ist als ein Destruktor im Main Thread, oder dass dieser Destruktor
> überhaupt ein Bottleneck ist.

das ist für die Fälle um die es hier geht common knowledge, ich habe es 
aber auch überprüft, weil ich es nicht glauben wollte.

Im Thread Beitrag "Zeitaufwand für unique_ptr.release()" habe ich, 
abgesehen von einem ersten dummen Programmierfehler, auch Messergebnisse 
gepostet:

10^9  3672 ms
10^8   361 ms
10^6  3147 µs
10^4    29 µs

Die erste Spalte gibt den Buffersize in doubles an.

> - in checkForBuffersToFree
> if (buffer->getReferenceCount() == 2)                          // [3]
>             buffers.remove (i);
> Hier wird weder garantiert, dass das der Destruktor der entsprechende
> Instanz sofort aufgerufen wird, noch dass es jemals in diesem Thread
> passieren wird. Zwischen dem Vergleich und dem remove kann in dem
> anderen Thread alles mögliche passieren, da die Syncronisation fehlt.
> Unter anderem kann der ReferenceCount inkrementiert werden,

Hmm, da werde ich mal einen Langzeit-Test machen. Theoretisch müsste das 
eigentlich passieren können. Wenn der Hintergrund thread genau in dem 
Moment zuschlägt, wo der Buffer allokiert wurde, aber noch nicht in 
currentBuffer = newBuffer gespeichert wurde.

Das darf doch wohl nicht wahr sein? Das ist gar nicht sooo 
unwahrscheinlich?

Herrlich, eine Menge zu testen :-)


vlg
 Timm

von Mikro 7. (mikro77)


Lesenswert?

Sebastian V. schrieb:
> Mich würde aber mal interssieren ob du dazu irgendwelche
> Performance Vergleiche hast.

Nein.

> Ich könnte mir vorstellen, dass sich da
> überhaupt nur unter recht speziellen Umständen überhaupt ein Unterschied
> messen lässt.

Richtig.

Auf einem Single Core oder stark ausgelasteten System mit tausenden 
Aufrufen /s kann es passieren, dass der Consumer nicht schnell genug zum 
Zug kommt und sich die Queue dadurch (stark) füllt, was wiederum zu 
teuren mallocs führen kann. Das ist dann aber schon ein sehr spezielles 
Szenario.

Unabhängig davon kann eine Begrenzung der Länge der Queue bei 
asynchroner Kommunikation grundsätzlich sinnvoll sein (High / Low Water 
Mark) für den Fall dass der Producer (zeitweise) schneller als der 
Consumer ist. Ansonsten ist Out of Memory der offensichtliche Fall. 
Subtiler wird es, wenn "lediglich" die Queue sehr lang wird, was sich 
überproprotional auf das Gesamtsystem auswirkt (teuere mallocs wegen 
langer Listen, oder gar Paging).

von lalala (Gast)


Lesenswert?

Mikro 7. schrieb:
> Das ist dann aber schon ein sehr spezielles Szenario.

Ich haette da eher ein anderes Szenario: funktionieren denn new und 
delete auf Betriebsystemebene fuer das gleiche Programm paralell? Also 
im Hintergrundthread wird fuer so 3609ms der Speicher freigegeben, und 
im Mainthread muss mit new ein kleiner Speicherbereich abgefordert 
werden. Muss der warten?

von mh (Gast)


Lesenswert?

Timm R. schrieb:
> das ist für die Fälle um die es hier geht common knowledge, ich habe es
> aber auch überprüft, weil ich es nicht glauben wollte.
>
> Im Thread Beitrag "Zeitaufwand für unique_ptr.release()" habe ich,
> abgesehen von einem ersten dummen Programmierfehler, auch Messergebnisse
> gepostet:
>
> 10^9  3672 ms
> 10^8   361 ms
> 10^6  3147 µs
> 10^4    29 µs

In dem Programm testest du etwas komplett anderes. Der Zeitaufwand für 
ein ptr.release ist relativ uninteressant ohne zu wissen, ob ptr.release 
ein Flaschenhals in deiner Anwendung ist (bzw. ich bekomme bis zu Faktor 
100 kleinere Werte für Zeiten).

Timm R. schrieb:
> Hmm, da werde ich mal einen Langzeit-Test machen. Theoretisch müsste das
> eigentlich passieren können.

Es ist ziemlich egal, wie wahrscheinlich oder unwahrscheinlich das ist, 
das riecht ziemlich nach undefined behavior.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

so, habe mir das jetzt alles langsam durch den Kopf gehen lassen.

@Sebastian
>Bevor der Hintergrund Thread sich dann wirklich schlafen legt schaut er
>nochmal in die Liste und nur wenn die leer ist legt er sich schlafen.

Ja, das ist der Knackpunkt! Da hätte ich auch drauf kommen können, 
dieses Denken in Threads ist noch etwas anstrengend.

Vielen Dank auf für Deinen Quelltext.

1. So wie Sebastian das gemacht hat, finde ich das sehr übersichtlich 
und leicht nachvollziehbar, diesen Code versteht man auch nach 10 Jahren 
noch ohne große Klimmzüge und man kriegt ihn nicht schnell kaputt. Der 
Code in besagtem Tutorial ist nur auf den ersten Blick einfach, es kommt 
wirklich auf Details an, Jules Variante kann man sehr leicht zerbrechen.

Hier ist die MCPP Variante für mich klar im Vorteil

2. Den Mutex zu akquirieren kostet im Vordergrund-Thread 100 ns, aber 
inkl. Notify geht das dann doch mal auf 1 µs, das ist schon recht lahm 
muss ich sagen, aber schnell genug. Aber immerhin hängt die Zeit ja 
nicht von der Größe des freizugebenden Puffers ab, das ist schonmal gut.

Hier ist aber Jules Variante im Vorteil, die hat keinen Overhead im 
Vordergrundthread, aber die µs dürfte wohl meistens drin sein.

@Mikro 77
Auch vielen Dank! Gute Hinweise, dass der unlock nicht praxisrelevant 
ist, heißt nichts. Es geht ja ums lernen.

Die lock free Strukturen sind allerdings brutaler Overkill und ich 
fürchte, dass, wenn man diese std::atomic Speicherbarrieren nicht fast 
täglich inhaliert, kriegt man da sehr schnell einen Knoten ins Gehirn.

Ich werde sicherlich mal versuchen, das zu verstehen, aber das ist schon 
sehr nach an der Maschine.

@mh
Ich bin das noch mal Schritt für Schritt durchgegangen, und zu dem 
Ergebnis gekommen, dass Jules Code wohl für den Anwendungsfall sicher 
ist.

Die Sache mit dem Flaschenhals ist bei diesen Anwendungen etwas anders 
gelagert. Es gibt bei diesen Programmen ein Audio Callback und im 
Prinzip stellt der Benutzer ein, wie häufig der aufgerufen wird. Alle 23 
ms ist die absolute Untergrenze, alle 1 ms muss man schon hinkriegen und 
super wäre schon, wenn ich alle 0.3 ms hinbekommen würde. Natürlich muss 
die Funktion in der Zeit auch einiges erledigen, zum Beispiel den Puffer 
beschreiben, zum Beispiel Midi Events auswerten, Audiosamples umrechnen 
und es gibt ja noch andere Programmteile, die Zeit brauchen. Wenn man 
bei einer Periode von 0.3 ms allein schon 0.1 ms mit dem Freigeben eines 
Puffers zubringt, ist natürlich der Ofen aus.


Im Endeffekt war es wohl eine sehr einfache Frage, aber ich bin einfach 
nicht auf den entscheidenen Punkt gekommen, vielen Dank an euch alle für 
die super hilfreichen und zielführenden Gedanken!


vlg
 Timm

von guest (Gast)


Lesenswert?

Timm R. schrieb:
> Die Sache mit dem Flaschenhals ist bei diesen Anwendungen etwas anders
> gelagert. Es gibt bei diesen Programmen ein Audio Callback und im
> Prinzip stellt der Benutzer ein, wie häufig der aufgerufen wird. Alle 23
> ms ist die absolute Untergrenze, alle 1 ms muss man schon hinkriegen und
> super wäre schon, wenn ich alle 0.3 ms hinbekommen würde. Natürlich muss
> die Funktion in der Zeit auch einiges erledigen, zum Beispiel den Puffer
> beschreiben, zum Beispiel Midi Events auswerten, Audiosamples umrechnen
> und es gibt ja noch andere Programmteile, die Zeit brauchen. Wenn man
> bei einer Periode von 0.3 ms allein schon 0.1 ms mit dem Freigeben eines
> Puffers zubringt, ist natürlich der Ofen aus.

Wenn es wirklich auf Performance ankommt, dann gibt man einmal 
allokierte Buffer nicht andauernd wieder frei, man recycled sie! Im 
schlimmsten Fall schreibt man sich sogar eine eigene Speicherverwaltung 
dafür. Gerade bei Audio kann man in den allermeisten Fällen ja vorher 
ausrechnen wie groß irgendwelche Buffer sein müssen und wieviel man 
davon braucht.

Zum Code von Sebastian: Das notify_all ist da eher kontraproduktiv. Da 
eh nur ein Element in die Queue gestellt wird bzw, es eh nur einen 
wartenden Thread gibt wäre da ein notify_one die bessere Wahl.
In dem workerThread jedes Element einzeln aus der Queue zu holen (und 
dabei dann auch jedesmal ein lock/unlock zu machen) ist von der 
Performance auch ungünstig. Wenn man schonmal den Lock hat sollte man 
die Queue in einem Rutsch komplett leermachen --> move in eine lokale 
tmp Queue, den Lock wieder freigeben und dann die temporäre Queue 
abarbeiten. In dem obigen Beispiel natürlich irrelevant, in eine 
konkreten Anwendung kann das den entscheidenden Unterschied machen. Wie 
oben schon jemand schrieb können Locks ziemlich teuer sein.

von Carl D. (jcw2)


Lesenswert?

Aus Performance-Gründen hat man bei Multicore-Systemen gerne je Core 
einen eigenen Heap. Wenn der "Deleter"-Thread nun (sehr warscheinlich) 
auf einem anderen Core läuft, dann muß der sich auf verschiedenen Ebenen 
mit dem "Allocator"-Thread synchronisieren. Sie prügeln sich im Cache um 
den gleichen Speicherbereich mit der Verwaltungsinformation, beide 
Threads müssen über Mutex Exklusivzugriff darauf bekommen.
Wenn es um Speed und Multicore geht, dann versucht man solche Dinge zu 
entzerren und nicht fester zusammenzubinden.

von Mikro 7. (mikro77)


Lesenswert?

Carl D. schrieb:
> Aus Performance-Gründen hat man bei Multicore-Systemen gerne je Core
> einen eigenen Heap.

Interessant. Insbesondere da die Threads die Cores wechseln. Quelle?

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo guest,

guest schrieb:

> Zum Code von Sebastian: Das notify_all ist da eher kontraproduktiv. Da
> eh nur ein Element in die Queue gestellt wird bzw, es eh nur einen
> wartenden Thread gibt wäre da ein notify_one die bessere Wahl.

oh Gott, wie blöd. Ich weiß ja, dass es notify_one gibt, sehr guter 
Hinweis!
Natürlich aus Prinzip, aber notify_one ist auch etwas schneller (930 ns 
vs. 1100 ns) immerhin einfach mal so 15 %, einfach nur als Lohn fürs 
richtig machen!

> In dem workerThread jedes Element einzeln aus der Queue zu holen (und
> dabei dann auch jedesmal ein lock/unlock zu machen) ist von der
> Performance auch ungünstig. Wenn man schonmal den Lock hat sollte man
> die Queue in einem Rutsch komplett leermachen --> move in eine lokale
> tmp Queue, den Lock wieder freigeben und dann die temporäre Queue
> abarbeiten. In dem obigen Beispiel natürlich irrelevant, in eine
> konkreten Anwendung kann das den entscheidenden Unterschied machen. Wie
> oben schon jemand schrieb können Locks ziemlich teuer sein.

Genau, in meinem Fall nicht relevant, es kann eigentlich immer nur einen 
Puffer geben, zwei wäre schon ein Krisenfall. Trotzdem, es geht ja um 
Timm lernt. Move ist ein schöne Idee und daran, dass einen Mutex zu 
locken teuer ist muss man auch immer denken. Und genau sowas sind die 
kleinen Hinweise durch die man was lernt. Gerade bei concurrency sieht 
man doch als Anfänger den Wald vor lauter Bäumen nicht.

Danke!

vlg

 Timm

von Carl D. (jcw2)


Lesenswert?

Mikro 7. schrieb:
> Carl D. schrieb:
>> Aus Performance-Gründen hat man bei Multicore-Systemen gerne je Core
>> einen eigenen Heap.
>
> Interessant. Insbesondere da die Threads die Cores wechseln. Quelle?

Unbedeutende Quellen, wie Intel, IBM, Oracle aus den letzten 10 Jahren.
Nur Leute, die "von Multicore-Maschinen keine Ahnung haben".
Google Suche "allocation of memory and multi core"
(sorry, einen richtigen Link wollte mein Pad nicht reinkopieren)

Die Frage ist nicht, ob die Threads die Cores wechseln, was ein 
vernünftiges OS soweit wie möglich umsomehr verhindern sollte, wie die 
Cores getrennte Caches/lokale-(NUMA-)Speicherbereiche benutzen, sonder 
ob sie sich beim Speicher-Allokieren-Deallokieren im selben 
Speicherbereich treffen.

Es gibt in einigen der Links Messungen, bei denen 1 Thread 100mal 
schneller ist, als 8 Threads, wenn diese ständig Speicher aus dem selben 
Topf holen/zurückgeben.
Der TO hat übrigens genau diese Messung durchgeführt.

Hier zeigt einer der Besten seines Fachs, welche diversen 
Allokationverfahren es gibt und wie man sie mit C++11 ff. einsetzen 
kann:
https://www.youtube.com/watch?v=LIb3L4vKZ7U

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Carl D. schrieb:

> Es gibt in einigen der Links Messungen, bei denen 1 Thread 100mal
> schneller ist, als 8 Threads, wenn diese ständig Speicher aus dem selben
> Topf holen/zurückgeben.
> Der TO hat übrigens genau diese Messung durchgeführt.

TO, das bin ich, magst Du kurz ausführen, wo ich diese Messung 
durchgeführt habe? Das würde mich schon interessieren ...


vlg
 Timm

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Timm R. schrieb:

> TO, das bin ich, magst Du kurz ausführen, wo ich diese Messung
> durchgeführt habe? Das würde mich schon interessieren ...

ja super toll, könnte dann der Herr Downvoter vielleicht erklären, wo 
ich so eine Messung gemacht habe und warum? Statt sich einfach in 
geheimnisvoll bekundeter Überlegenheit zu sonnen? Vielleicht bin ich ja 
der einzige, der es nicht schnallt, dann wäre es aber sehr sehr lieb, 
wenn mich mal irgendwer erlösen würde. Meine Speicherblock 
Allokationsmessungen waren jedenfalls single thread und die Messungen in 
diesem Thread bezogen sich nur auf die reine Ausführungsdauer von 
notify_one, notify_all und std::lock_guard.

vlg
 Timm

Beitrag #5137923 wurde von einem Moderator gelöscht.
Beitrag #5137958 wurde von einem Moderator gelöscht.
Beitrag #5138079 wurde von einem Moderator gelöscht.
Beitrag #5138528 wurde von einem Moderator gelöscht.
Beitrag #5139168 wurde von einem Moderator gelöscht.
Beitrag #5139990 wurde von einem Moderator gelöscht.
Beitrag #5140103 wurde von einem Moderator gelöscht.
Beitrag #5140251 wurde von einem Moderator gelöscht.
Beitrag #5140389 wurde von einem Moderator gelöscht.
Beitrag #5142600 wurde von einem Moderator gelöscht.
Beitrag #5143776 wurde von einem Moderator gelöscht.
Beitrag #5144969 wurde von einem Moderator gelöscht.
Beitrag #5145235 wurde von einem Moderator gelöscht.
Beitrag #5145969 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.