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
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
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
> 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.
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.
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
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
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.
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.
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.
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
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).
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?
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.
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
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.
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.
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?
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
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
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.