From 9f0d7cc796655f6dfe309c611f5a4ee3f049befd Mon Sep 17 00:00:00 2001
From: Ichthyostega
Date: Thu, 28 May 2026 02:18:06 +0200
Subject: [PATCH] BufferProvider: investigate use of the ComputationalLoad with
a memory buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The intention is to utilise this helper class, that was developed for the
scheduler stress testing; but this time, it should access the actual allocated
render buffers to generate the load.
This implies to use buffers of a significant size; it turned out that
the existing implementation was imprecise, insofar it always used full multiples
of the configured buffer size, which defeats the purpose of performing
a precisely calibrated number of calculation steps. As long as the buffer size
would remain comparatively small, this drift does not matter much.
Thus I changed the implementation now to ensure that exactly the calibrated
number of steps is performed, using a modulus to map it to the buffer memory.
Furthermore, I plan to use a random-walk pattern in the worker thread
to select from a collection of pre-established »buffer types«, similar
to what happens in the actual engine
---
src/lib/meta/util.hpp | 6 +-
src/test/run.hpp | 2 +-
src/vault/mem/engine-buffer-manager.hpp | 1 +
tests/47engine.tests | 5 +
tests/vault/gear/test-chain-load-test.cpp | 17 +-
tests/vault/gear/test-chain-load.hpp | 22 +-
tests/vault/mem/buffer-provider-load-test.cpp | 163 +++++
wiki/thinkPad.ichthyo.mm | 602 +++++++++++++++---
8 files changed, 731 insertions(+), 87 deletions(-)
create mode 100644 tests/vault/mem/buffer-provider-load-test.cpp
diff --git a/src/lib/meta/util.hpp b/src/lib/meta/util.hpp
index 12a455e90..7fb4967be 100644
--- a/src/lib/meta/util.hpp
+++ b/src/lib/meta/util.hpp
@@ -505,19 +505,19 @@ namespace util {
/* === Literals for common size designations === */
-inline uint
+constexpr inline uint
operator""_KiB (ullong const siz)
{
return uint(siz) * 1024u;
}
-inline uint
+constexpr inline uint
operator""_MiB (ullong const siz)
{
return uint(siz) * 1024u*1024u;
}
-inline ullong
+constexpr inline ullong
operator""_GiB (ullong const siz)
{
return siz * 1024uLL*1024uLL*1024uLL;
diff --git a/src/test/run.hpp b/src/test/run.hpp
index b353b062c..c403100b3 100644
--- a/src/test/run.hpp
+++ b/src/test/run.hpp
@@ -57,7 +57,7 @@ namespace test {
virtual void run(Arg arg) = 0;
void seedRand(); ///< draw a new random seed from a common nucleus, and re-seed the default-Gen.
- lib::Random makeRandGen(); ///< build a dedicated new RandomGen, seeded from the default-Gen
+ static lib::Random makeRandGen(); ///< build a dedicated new RandomGen, seeded from the default-Gen
static string firstTok (Arg); ///< conveniently pick the first token from the argument line
static uint firstVal (Arg, uint =1); ///< conveniently use some number given as argument, with optional default
};
diff --git a/src/vault/mem/engine-buffer-manager.hpp b/src/vault/mem/engine-buffer-manager.hpp
index a161d54a4..bc8d36bb1 100644
--- a/src/vault/mem/engine-buffer-manager.hpp
+++ b/src/vault/mem/engine-buffer-manager.hpp
@@ -166,6 +166,7 @@ namespace mem {
/****************************************************************//**
* Core function: satisfy a request for allocation
+ * @param sizRequest minimum required buffer size to allocate, in bytes.
* @return an \ref Alloc struct representing the desired allocation,
* can be empty to signal that the request can not be satisfied.
* @warning whoever accepts a non-empty result from this call
diff --git a/tests/47engine.tests b/tests/47engine.tests
index 0612016a2..2820b2ef8 100644
--- a/tests/47engine.tests
+++ b/tests/47engine.tests
@@ -17,6 +17,11 @@ return: 0
END
+TEST "Buffer provider integration" BufferProviderLoad_test < 5ms, probably due to cache effects.
+ * Furthermore, in the actual scheduler load testing, which uses this
+ * computational load in a more realistic setting (as opposed to a tight
+ * microbenchmark loop), observed run times are consistently slightly
+ * longer than calibrated (cache effects, pipeline stalls)
*/
void
verify_computation_load()
@@ -892,8 +901,10 @@ namespace test {
cpuLoad.useAllocation = false;
micros = cpuLoad.invoke();
CHECK (micros > 900);
+ CHECK (micros < 1100);
micros = cpuLoad.invoke(5);
CHECK (micros > 4600);
+ CHECK (micros > 5400); // usually quite precise, less than 1% off
micros = cpuLoad.invoke(10);
CHECK (micros > 9500);
micros = cpuLoad.invoke(100);
@@ -903,11 +914,11 @@ namespace test {
micros = cpuLoad.invoke();
CHECK (micros > 900);
micros = cpuLoad.invoke(5);
- CHECK (micros > 4600);
+ CHECK (micros > 4600); // can be significantly larger, like 7ms
micros = cpuLoad.invoke(10);
- CHECK (micros > 9500);
+ CHECK (micros > 9500); // can be significantly larger, like 14ms
micros = cpuLoad.invoke(100);
- CHECK (micros > 95000);
+ CHECK (micros > 95000); // can be significantly larger, like 140ms
}
diff --git a/tests/vault/gear/test-chain-load.hpp b/tests/vault/gear/test-chain-load.hpp
index bee882f23..f851d31db 100644
--- a/tests/vault/gear/test-chain-load.hpp
+++ b/tests/vault/gear/test-chain-load.hpp
@@ -1306,6 +1306,9 @@ namespace test {
* speed in a microbenchmark with `LOAD_BENCHMARK_RUNS`
* repetitions; the result is stored in a static
* variable and can thus be reused.
+ * @see TestChainLoad_test::verify_computation_load
+ * @warning for longer times, especially the memory load performs
+ * increasingly slower than calibrated. See the details in the test.
*/
class ComputationalLoad
: util::MoveAssign
@@ -1368,9 +1371,10 @@ namespace test {
}
size_t
- requiredBuffSiz (uint scaleStep) const
+ requiredBuffSiz (uint scaleStep) const ///< @return required size for external buffer in bytes.
{
- return useAllocation? allocNeeded(scaleStep).first : 0;
+ return sizeof(size_t)
+ * (useAllocation? allocNeeded(scaleStep).first : 0);
}
private:
@@ -1385,11 +1389,11 @@ namespace test {
allocNeeded (uint scaleStep) const
{
auto cnt = roundsNeeded(scaleStep);
- auto siz = max (scaleStep * sizeBase, 1u);
+ auto siz = max (scaleStep * sizeBase, 2u);
auto rep = max (cnt/siz, 1u);
// increase size to fit
siz = cnt / rep;
- return make_pair (siz,rep);
+ return make_pair (siz,cnt);
}
void
@@ -1407,15 +1411,17 @@ namespace test {
void
causeMemProcessLoad (uint scaleStep)
{
- auto [siz,round] = allocNeeded (scaleStep);
+ auto [siz,cnt] = allocNeeded (scaleStep);
lib::UninitialisedDynBlock memBlock;
size_t* mem = externalBuff? externalBuff
: memBlock.allocate (siz);
Sink sink;
*mem = sink+1;
- for ( ; 0 < round; --round)
- for (size_t i=0; i
+
+ **Lumiera** is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version. See the file COPYING for further details.
+
+* *****************************************************************/
+
+/** @file engine-buffer-manager-test.cpp
+ ** unit test \ref BufferProviderLoad_test
+ */
+
+
+#include "test/run.hpp"
+#include "test/test-helper.hpp"
+#include "vault/mem/engine-buffer-manager.hpp"
+#include "vault/mem/engine-buffer-allocator.hpp"
+#include "vault/gear/test-chain-load.hpp"
+#include "lib/scoped-collection.hpp"
+#include "lib/iter-explorer.hpp"
+#include "lib/sync-barrier.hpp"
+#include "lib/thread.hpp"
+#include "lib/symbol.hpp"
+#include "lib/random.hpp"
+
+#include
+#include
+#include
+
+using lib::explore;
+using lib::Random;
+using lib::Literal;
+using lib::Thread;
+using lib::SyncBarrier;
+using vault::gear::test::ComputationalLoad;
+using std::this_thread::yield;
+using std::this_thread::sleep_for;
+using std::chrono::microseconds;
+using std::chrono_literals::operator ""us;
+using std::chrono_literals::operator ""ms;
+
+namespace vault {
+namespace mem {
+namespace test {
+
+ namespace { // Test parameters......
+ const size_t ALLOC_REQ = 1024*1024; ///< use test-allocation request of 1 MiB size
+ const size_t NUM_THREADS = 100; ///< press concurrently
+ }
+
+
+
+
+ /*****************************************************************************//**
+ * @test Show how the central buffer manager of the Render Engine can be accessed.
+ */
+ class BufferProviderLoad_test : public Test
+ {
+
+ virtual void
+ run (Arg)
+ {
+ demonstrate_EngineSetup();
+ verify_massiveOverload();
+ verify_controlledLoad();
+ }
+
+
+
+ /** @test show a setup of the buffer manager that is
+ * structurally equivalent to the Render Engine.
+ */
+ void
+ demonstrate_EngineSetup()
+ {
+ }
+
+
+
+ struct DefaultSetup
+ {
+ static constexpr uint NUM_JOBS = 10;
+ static constexpr size_t BASE_BUFFSIZ = 100_KiB;
+
+ /// local random generator per thread
+ Random rand_;
+ ComputationalLoad load_;
+
+ DefaultSetup()
+ : rand_{makeRandGen()}
+ {
+ load_.timeBase = 1ms;
+ load_.sizeBase = BASE_BUFFSIZ;
+ load_.useAllocation = true;
+ }
+ };
+
+ /**
+ * Emulation of the load produced by a worker thread
+ */
+ template
+ class DummyWorkerThread
+ : CONF
+ , public Thread
+ {
+
+
+ void
+ workActivity()
+ {
+ for (uint i=0; i
-
+
+
@@ -143946,7 +143947,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -143955,6 +143956,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -144000,10 +144002,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
-
+
+
+
+
@@ -144083,7 +144085,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144107,10 +144109,21 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
+
+
+
+ die Lösung ist dann, diesen Lookup in einem »thread-local Slice« zu machen
+
+
+
+
-
+
+
@@ -144119,6 +144132,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -144238,9 +144252,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
+
@@ -144843,12 +144857,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -144878,7 +144896,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -144889,7 +144908,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144906,7 +144925,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
@@ -144924,7 +144945,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -144946,8 +144967,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
+
+
@@ -144964,7 +144985,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
+
+
+
+
@@ -144974,12 +145001,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
-
+
@@ -145327,7 +145355,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -145359,7 +145388,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -145372,7 +145402,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -145406,7 +145437,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -145440,7 +145472,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -145449,8 +145481,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
-
+
@@ -145459,6 +145492,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -145473,9 +145507,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
-
+
@@ -145484,14 +145520,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
+
@@ -145592,15 +145628,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
-
-
-
+
...denn nach dem Thread-Ende wird die Allokation automatisch zurückgeführt, so daß sich der Effekt nicht mehr beobachten läßt
@@ -145615,24 +145649,19 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
...wenn man das erst mal akzeptiert, ist es auch nicht so schlimm: man inkrementiert einen Atomic
-
-
+
-
-
-
+
ein weiters Announcement,
@@ -145644,11 +145673,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
wird transparent ignoriert
-
-
+
-
+
@@ -145678,9 +145706,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
meine Idee der Heuristik umsetzen
@@ -145689,8 +145715,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
mit einzelnen Grenz-Szenarien durchspielen
-
-
+
@@ -145700,7 +145725,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
+
+
+
+ ...wurde nun doch ziemlich vollständig entwickelt (ich bin schlecht im opportunistischen Übersehen von Problemen)
+
+
+
+
@@ -145761,16 +145795,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
das hier ist keine generische Library, sondern ein Application-Framework. Auch »Vault« ist inhaltlich genau auf Lumiera ausgerichtet, der Unterschied liegt nur im Grad der Integration mit Application-level Services
-
-
+
@@ -146329,7 +146360,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -147050,16 +147081,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
-
-
+
Wenn man eine größere Kapazität reserviert, aber dann wiederholt immer nur einzelne Buffer belegt und wieder freigibt, sollte relativ bald der lokale Pool getrimmt werden, und die freigegebenen Buffer an den globaeln Pool zurückgessend
-
-
+
@@ -147068,6 +147096,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -148328,6 +148357,418 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
+
+ ...und nicht mehr; im Besonderen keine Streßtests und keine Performance-Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ die sich jedoch ggfs. auf Null herunterregeln läßt, um »Vollast« zu erzeugen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ComputationalLoad ist eine in-sich geschlossene Klasse, die in test-chain-load.hpp definiert (und verwendet) wird.
+
+
+ -
+ man gibt eine erwartete Dauer vor
+
+ -
+ optional kann man auch eine Speichergröße vorgeben und die »memory«-Methode aktivieren
+
+ -
+ dann kann/sollte man einmal die statische Speed-Konstante für die aktuelle Plattform kalibrieren
+
+ -
+ danach weiß die ComputationalLoad, wieviele Runden zu drehen sind
+
+ -
+ für den eigentlichen Aufruf kann man dann das N-fache dieser kalibrierten Basis-Last anfordern
+
+
+
+ Für den aktuellen Fall interesant wäre weniger die CPU-Methode, sondern die Memory-Methode: hierbei wird ein Heap-Puffer »bespielt«, indem man dort zufällig liegende Daten addiert. In der aktuellen Implementierung wird dieser Block direkt bei Bedarf vom Heap alloziert, als UninitialisedDynBlock. Man müßte also eine geeignete Variation schaffen, für die man einen bestehenden Speicherblock übergibt
+
+
+
+
+
+
+
+
+
+ Aktuell ist die ComputationalLoad weitgehend in-sich abgeschlossen, so daß man nichts über die Implementierung wissen muß. Im Besonderen für die Mem-Methode wird eine dynamische Allokation passender Größe unmittelbar getätigt — die Größe hängt von den Parametern ab. Die Implementierung der Last-Funktion ist kompakt und gut lesbar, weil sie sich das API des UninitialisedDynBlock zunutze macht.
+
+
+
+
+
+ Da müßte ich nun eine »Hintertür« einbauen und dafür auch einen Ersatz für memBlock.front() und back() finden; zudem wäre dann der Aufrufende dafür verantwortlich, daß genügend Speicher gegeben ist, und dafür müßte eine Hilfsmethode bereitgestellt werden
+
+
+
+
+
+
+
+
+
+ Mit etwas Geschick dürfte sich der Schaden in Grenzen halten.
+
+
+ Die Abwägung ist eindeutig:
+
+
+ -
+ längerfristig ist es unbedingt erforderlich, auch tatsächlich auf den allozierten Buffer vollumfänglich mit Rechenoperationen zuzugreifen
+
+ -
+ ComputationalLoad ist bereits pefekt dafauf ausgelegt, bietet die durchaus trickreiche Kalibrierung und ist ausgetestet
+
+ -
+ es muß bei einer einzigen Berechnungsfunktion bleiben, sonst leidet die Klarheit
+
+ -
+ dieser Code ist Testcode, und befindet sich auf Implementierungs-Level, so daß man korrekte Anwendung einfordern kann.
+
+ -
+ der Rest sind »ästhetische Schmerzen« — ich kann nämlich im Kern auf Pointer-Arrithmetik zurückfallen, dann ist es halt nicht ganz so elegant...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ konkret müssen giftige Situationen provoziert werden, um zu sehen daß der Code selbst dann nicht entgleist, wenn man ihn an die Grenzen treibt. Diese Grenzen sind jedoch ohne eine vertiefte Analyse nicht bekannt, und diese Analyse möchte ich (jetzt aus Zeitgründen) nicht machen. Daher der Ansatz, möglichst viel relevante Unregelmäßigkeiten reinzubringen
+
+
+
+
+
+
+
+
+
+
+ ....was bedetuet, daß man mit den Dimensionen des einzelnen Jobs spielen können möchte, um ggfs. Verhaltensmuster zu erkennen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ denn:
+
+
+ -
+ die Polify wird vor dem Thread-start initialisiert
+
+ -
+ aber die Felder in der Worker-Klasse selber werden nach dem Thread-start initialisiert und stellen somit eine Race-condition dar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ der default-Wert sind 1KiB — aber die zu erwartenden Repetitionen für 100µs sind bereits so ca 10k. Deshalb wird mit diesen Standard-Settings der Buffer x mal bespielt, und für etwas größere Last-Zeiten (1ms und mehr) fällt die Granularität des Buffers kaum noch ins Gewicht
+
+
+
+
+
+
+
+
+
+
+ ...hier für einen Buffer-Manager-Test würde ich natürlich gern größere Buffer bespielen, 1MiB oder mehr. Die Schleifen-Logik in der ComputationalLoad-Implementierung ist einfach, es wird N mal der ganze Speicherblock bespielt, und zwar mindestens ein mal....
+
+
+
+
+
+
+
+
+
+ Extrembeispiel: angenommen, die Kalibrierung ist nahe am default, also 100 Schritte pro µs, dann brauchen wir 10000 Schritte für 100µs. Wenn der Buffer aber 1 MiB groß ist, machen wir 1 Mio Schritte. Das bedeutet, egal was wir vorgeben, wir landen bei ~10ms. Die Kalibrierung wird sich zwar »auf den Kopf stellen«, aber das Aufrunden verhindert, das die Kalibrierung zum Tragen kommt.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ich behalte aber die »Aufrunde-Logik« für die size bei (die Anzahl Repetions ist kleiner als exakt, bedingt durch die Ganzzahl-Division; dem entsprechend wird die Size durch zu wenig dividiert und fällt damit größer an, als exakt notwendig wäre.
+
+
+
+
+
+ Aber nun lasse ich die Schleife stets bis zur kalibrierten Rundenzahl laufen, und lediglich die Schreib-Position rotiert, gesteuert durch einen modulus auf (size - 1), da wir ja eine zusätzliche Speicherzelle brauchen, um jeweils die summe mit dem Vorgänger reinzuschreiben.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ich deute das als Cache-Effekte
+
+
+
+
+
+
+
+
+
+
+ 5ms ziemlich exakt, 10ms ⟶ 10.2ms, aber 100ms ⟶ 140ms
+
+
+
+
+
+
+
+
+
+
+
+ Die Granularität spielt nach dem Umbau nur noch indirekt eine Rolle — wir verwenden dann halt den Buffer nur partiell.
+
+
+
+
+
+ Habe mir die Zahlen angeschaut, für kleine Zeiten < 5ms sind beide Methoden ziemlich genau, aber die Mem-Metode wird dann deutlich langsamer, vor allem bei größerem Speicherblock. Das muß man dann einfach bedenken, es handelt sich in diesem Fall also um eine mindest-Last, und für genauere Untersuchungen wird man nicht umhin kommen, die exakte Laufzeit im einzelnen Job zu erfassen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ...das ist sogar wichtig, damit wir uns ähnlich wie in der echten Engine verhalten, incl. Zugriff über double-checked locking;
+
+
+
+
+
+ Für das korrekte Test-Setup ist es lediglich erforderlich, daß effektiv die instrumentierung stattfindet bevor der jeweilige Test-Worker gestartet wird.
+
+
+
+
+
+
+
+
+
+
+
+
+ ...es soll möglichst die echte Job-Struktur nachgeahmt werden; das bedeutet, daß die BuffDescr bereits im Node-Graphen liegen, wohingegen die BuffHandle erst in der Call-Sequenz selber erzeugt werden....
+
+
+ Ich kann also doch nicht so einfach einen Random-Walk über die Puffergröße machen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -148377,6 +148818,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
@@ -148384,7 +148829,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
@@ -148401,6 +148846,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
@@ -148430,6 +148876,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
+
+
+
+
+
@@ -152899,8 +153350,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
-
+
+
@@ -181075,7 +181527,7 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
-
+
@@ -183875,7 +184327,7 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
-
+
@@ -184045,6 +184497,12 @@ unsigned int ThreadIdAsInt = *static_cast<unsigned int*>(static_cast<vo
+
+
+
+
+
+