diff --git a/src/lib/local-slice.hpp b/src/lib/local-slice.hpp index 22c8132af..7978a95b8 100644 --- a/src/lib/local-slice.hpp +++ b/src/lib/local-slice.hpp @@ -27,10 +27,12 @@ ** ** @warning as of 2022 the implementation of `thread_local` in the GNU runtime ** was observed to incur seemingly unnecessary overhead, which however - ** is relevant only in a high-performance environment. - ** @todo WIP-WIP 4/2026 prototyping an Engine Buffer manager /////////////////////////////////////////////TICKET #1425 + ** is relevant only in a high-performance environment. However, some + ** [casual benchmarks](\ref LocalSlice_test::investigatePerformance) + ** indicated that the problem does not seem to be _that serious_... ** @see LocalSlice_test - ** @see ////TODO usage example + ** @see LocalBufferStage (usage example) + ** @see buffer-provider-load-test.cpp ** */ diff --git a/src/vault/mem/buffer-provider.hpp b/src/vault/mem/buffer-provider.hpp index 136f5de2c..4fd86a9c6 100644 --- a/src/vault/mem/buffer-provider.hpp +++ b/src/vault/mem/buffer-provider.hpp @@ -88,6 +88,16 @@ ** implementation; when used from the actual [Render Environment](\ref render-environment.hpp), ** this will be backed by a production-strenght and concurrency-safe implementation. ** + ** \par Production-grade implementation + ** For use in the actual Render Engine, operation within a massively concurrent environment + ** must be taken into account. In spring 2026, a first prototype of such an implementation + ** setup was created, using a thread-local back-end for both the _Stage_ and _Store_ parts. + ** The actual allocations are managed by \ref EngineBufferManager and will be transported + ** to and from the \ref LocalMemPool in each thread through an asynchronous messaging queue. + ** The number of allocations to retain in those thread-local memory pools is coordinated + ** by a heuristic, based on an efficiency score maintained at each local entry. + ** @todo 2026 if this scheme is able to deliver adequate performance remains to be seen... + ** ** \par Design critique ** In its current shape, BufferProvider is both a rather large front-end facade, ** while also containing some »code behind« to translate the API invocations into @@ -110,6 +120,7 @@ ** @todo 2026 we might consider adding alignment information to the associated TypeHandler. ** ** @see buffer-provider-protocol-test.cpp + ** @see buffer-provider-load-test.cpp ** @see output-slot.hpp ** @see engine-ctx.hpp ** @see proc-node.hpp diff --git a/src/vault/mem/engine-buffer-allocator.hpp b/src/vault/mem/engine-buffer-allocator.hpp index 346469ade..71411dbb6 100644 --- a/src/vault/mem/engine-buffer-allocator.hpp +++ b/src/vault/mem/engine-buffer-allocator.hpp @@ -19,7 +19,7 @@ ** are sent back and collected by the EngineBufferManager, which serves ** thus as an background collector and manager for the buffer memory. ** - ** @todo WIP-WIP 4/2026 The intention is to use a family of pools eventually, + ** @todo WIP 4/2026 The intention is to use a family of pools eventually, ** with some of the most relevant tile sizes. The EngineBufferManager ** would then actually retain ownership of all allocations and just ** _lease_ them to the LocalMemPool instances. Allocations would be @@ -44,33 +44,20 @@ #include "lib/error.hpp" #include "lib/util-quant.hpp" #include "lib/allocator-handle.hpp" -//#include "lib/hash-value.h" #include "vault/mem/engine-buffer-manager.hpp" -//#include "vault/mem/type-handler.hpp" -//#include "vault/mem/buffer-local-tag.hpp" #include "lib/nocopy.hpp" -//#include "lib/util.hpp" +#include +#include namespace vault{ namespace mem { - -// using lib::HashVal; -// using util::unConst; - -#include -#include namespace err = lumiera::error; using std::byte; - namespace { // config and internal helpers... - - } - - /* === Setup of »naïve« buffer allocation === */ @@ -137,10 +124,5 @@ namespace mem { - - - - - }} // namespace vault::mem #endif /*VAULT_MEM_ENGINE_BUFFER_ALLOCATOR_H*/ diff --git a/src/vault/mem/naive-buffer-setup.hpp b/src/vault/mem/naive-buffer-setup.hpp index 159ad187f..3501c2d86 100644 --- a/src/vault/mem/naive-buffer-setup.hpp +++ b/src/vault/mem/naive-buffer-setup.hpp @@ -17,10 +17,18 @@ ** a new heap block for any buffer, without deallocating memory ever. This allows to ** attach additional tracking status flags for each allocated block and validate ** allocator behaviour after the fact. + ** @remark for use in the actual Render Engine, a much more elaborate setup is necessary, + ** to avoid contention due to the centralised nature of memory allocation. In spring 2026, + ** an initial prototype was created, comprising + ** - LocalBufferStage + ** - LocalBufferStore + ** - EngineBufferManager + ** - EngineMetadata ** ** @see DiagnosticOutputSlot ** @see DiagnosticBufferProvider ** @see buffer-provider-protocol-test.cpp + ** @see buffer-provider-load-test.cpp ** @see render-environment.cpp */ diff --git a/tests/vault/mem/buffer-provider-load-test.cpp b/tests/vault/mem/buffer-provider-load-test.cpp index 1aa211702..8e3c8438e 100644 --- a/tests/vault/mem/buffer-provider-load-test.cpp +++ b/tests/vault/mem/buffer-provider-load-test.cpp @@ -18,6 +18,7 @@ #include "test/run.hpp" #include "test/test-helper.hpp" +#include "test/microbenchmark.hpp" #include "vault/mem/buffer-provider-setup.hpp" #include "vault/mem/engine-buffer-metadata.hpp" #include "vault/mem/engine-buffer-manager.hpp" @@ -30,22 +31,16 @@ #include "lib/sync-barrier.hpp" #include "lib/iter-stack.hpp" #include "lib/thread.hpp" -//#include "lib/symbol.hpp" #include "lib/random.hpp" -#include "test/diagnostic-output.hpp"////////////TODO - -//#include -//#include -#include using lib::Random; using lib::Depend; using lib::DependInject; using lib::explore; -//using lib::Literal; using lib::Thread; using lib::SyncBarrier; using lib::ScopedCollection; +using test::benchmarkTime; using vault::gear::test::ComputationalLoad; using std::this_thread::yield; using std::this_thread::sleep_for; @@ -59,7 +54,8 @@ namespace test { namespace { // ========== Test Setup ========== - const size_t MANY_THREADS = 100; ///< press concurrently + const size_t MANY_THREADS = 100; ///< press concurrently + const size_t FULL_LOAD_CORES = 4; ///< workers to use for a _full load run_ const size_t MAX_SCALE = 5; ///< maximum scale step for randomised buffer types @@ -158,7 +154,6 @@ namespace test { workActivity() { SyncBarrier::sync(); -cout<<"Start:"<cntEntries()) // 5 distinct buffer types registered CHECK (5 == mockEngine.metaHub->cntEntries()); sleep_for (500us); // additional delay for the local pool's destructor // (which is invoked after the thread has terminated) -SHOW_EXPR(watch(*mockEngine.globalPool).numAllocs()) // there must be some allocations, still sitting in the return queue CHECK (0 < watch(*mockEngine.globalPool).numAllocs()); @@ -357,14 +347,13 @@ SHOW_EXPR(watch(*mockEngine.globalPool).numAllocs()) // sent back by the local pool's destructor... mockEngine.globalPool->processPendingRequests(); // now there should be no memory leaks....... -SHOW_EXPR(watch(*mockEngine.globalPool).bytesLeased()); CHECK (0 == watch(*mockEngine.globalPool).bytesLeased()); } - /** @test - * + /** @test press the same setup using a + * massive overload of concurrent workers */ void verify_massiveOverload() @@ -379,24 +368,51 @@ SHOW_EXPR(watch(*mockEngine.globalPool).bytesLeased()); while (explore(threads).has_any()) sleep_for (5ms); -SHOW_EXPR("harumpfha") -SHOW_EXPR(mockEngine.metaHub->cntEntries()) -SHOW_EXPR(watch(*mockEngine.globalPool).numAllocs()) + CHECK (mockEngine.globalPool); + // again 5 distinct buffer types registered + CHECK (5 == mockEngine.metaHub->cntEntries()); + CHECK (0 < watch(*mockEngine.globalPool).numAllocs()); sleep_for (500us); mockEngine.globalPool->processPendingRequests(); -SHOW_EXPR(watch(*mockEngine.globalPool).bytesLeased()); - + CHECK (0 == watch(*mockEngine.globalPool).bytesLeased()); } - /** @test - * + /** worker configuration for seamless full load */ + struct FullLoadSetup : WorkSetup + { + static constexpr uint JOB_STEPS = 100; // number of processing steps in a single job + static constexpr uint STEP_SPREAD = 2; // reduced irregularity of allocations + static constexpr uint LOAD_SPREAD = 0; // effectively disable variation of buffer types + static constexpr uint MAX_DELAY = 0; // also disable random sleep between jobs + }; + + /** @test attempt to create a coordinated _full load_ + * - configure the workers to compute without pause + * - use more repetitions in a single job + * - configured load time should be 1000ms per thread + * - use small number of workers; each gets its own core + * - capture the overall run time of the workers + * - coordination overhead should not be excessive */ void verify_controlledLoad() { + Ctx mockEngine; + using Threads = ScopedCollection>; + + double time_us = benchmarkTime ([]{ // run a set of threads with full-load config... + Threads threads{FULL_LOAD_CORES, Threads::fill()}; + while (explore(threads).has_any()) + sleep_for (1ms); + }); + + CHECK (time_us < 1.6e6); + sleep_for (500us); + mockEngine.globalPool->processPendingRequests(); + CHECK (0 == watch(*mockEngine.globalPool).bytesLeased()); } }; diff --git a/tests/vault/mem/buffer-provider-protocol-test.cpp b/tests/vault/mem/buffer-provider-protocol-test.cpp index 90db6e0a8..7b349dbe3 100644 --- a/tests/vault/mem/buffer-provider-protocol-test.cpp +++ b/tests/vault/mem/buffer-provider-protocol-test.cpp @@ -71,6 +71,7 @@ namespace test { * for diagnostics and does not actually _manage_ buffers; rather each buffer * is allocated on the heap and never released, so that any allocation and * all buffer contents can be verified after the fact. + * @see BufferProviderLoad_test for a production-grade setup. */ class BufferProviderProtocol_test : public Test { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index d6d8f83f7..11d92836d 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1206,29 +1206,41 @@ Please note the shortcomings and logical contradictions in the solution currentl * The current design rather places the implementation according to the roles of the involved entities, which causes some ping-pong on the implementation level. Especially the ScopeLocator singleton can be accessed multiple times. This is the usual clarity vs. performance tradeoff. Scope resolution is assumed rather to be //not performance critical.// -
+
All rendering, transformations and output of media data requires using ''data buffers'' -- but the actual layout and handling of these buffers is closely related to the actual implementation of these operations. As we're relying heavily on external libraries and plug-ins for performing these operations, there is no hope getting away with just one single {{{Buffer}}} data type definition. Thus, we need to confine ourselves to a common denominator of basic operations regarding data buffers and abstract the access to these operations through a BufferProvider entity. Beyond these basic operations, mostly we just need to assure that //a buffer exists as an distinguishable element// -- which in practice boils down to pushing around {{{void*}}} variables. 
 
-Obviously, overloading a pointer with semantic meaning isn't exactly a brilliant idea -- and the usual answer is to embed this pointer into a smart handle, which also yields the nice side-effect of explaining this design to the reader. Thus a buffer handle
+Obviously, overloading a pointer with semantic meaning isn't exactly a brilliant idea -- and the usual answer is to embed this pointer into some wrapper or handle, which also yields the additional benefit of explaining this design to the reader. Thus a buffer handle
 * can only be obtained from a BufferProvider
 * can be used to identify a buffer
 * can be dereferenced
 * can be copied 
 
-!design quest: buffer type information
-To perform anything useful with such a buffer handle, the client code needs some additional information, which can be generalised into a //type information:// Either, the client needs to know the size and kind of data to expect in the buffer, maybe just assume to get a specific buffer with suitably dimensions, or the client needs to know which buffer provider to contact for any management operations on that buffer (handle). And, at some point there needs to be a mechanism to verify the validity of a handle. But all of this doesn't mean that it's necessary to encode or embedd this information directly into the handle -- it might also be stored into a registration table (which has the downside of creating contention), or it might just be attached implicitly to the invocation context.
+!A design challenge: buffer type information
+To perform anything useful with such a buffer handle, the client code needs some additional information, which can be generalised into a //type information:// Either, the client needs to know the size and kind of data to expect in the buffer, maybe just assume to get a specific buffer with suitably dimensions, or the client needs to know which buffer provider to contact for any management operations on that buffer (handle). And, at some point there needs to be a mechanism to verify the validity of a handle. But all of this doesn't mean that it's necessary to encode or embed this information directly into the handle -- it might also be stored into a registration table (which has the downside of creating contention), or it might just be attached directly or implicitly to the invocation context.
 
-Just linking this type information to the context is certainly the most elegant solution, but also by far the most difficult to achieve -- not to mention the implicit dependency on a very specific invocation situation. So for now (9/2011) it seems best to stick to the simple and explicit implementation, just keeping that structural optimisation in mind. And the link to this buffer type information should be made explicit within the definition anyway, even if we choose to employ another design tradeoff later.
-* thus the conclusion is: we introduce a ''descriptor object'', which will be stored within the handle
-* each BufferProvider exposes a ''descriptor prototype''; it can be specialised and used by to [[organise implementation details|BufferMetadata]]
+Within the realm of the Render Engine, various different activities are finally translated into some function that accesses a block of memory. However, this common scheme is only the point of access, since different kinds of processing will require different handling patterns -- and there is the danger that varying requirements will leak into the basic structure of the render processing. An ever increasing complexity in this central region of the application is seen as an essential threat -- and so ''abstraction'' is seen as the primary goal of this design.
+* whenever //some kind of buffer// needs to be used, a ''handle'' is passed
+* this handle is only marked with a hash-ID and a backlink to some //managing service//
+* which in turn must be structured in such a way that //differing back-ends can be built// -- to serve the different use cases
 
+The purpose of the Lumiera Render Engine is to coordinate the media processing -- but not to conduct the processing itself. Within the Render Node network, the different kinds of activities associated with „just a buffer“ can thus be translated into a ''buffer type''. For invoking and instructing render operations, this type remains opaque -- in accordance with the actual usage, this implied additional knowledge can even be separated into two different relations:
+* we introduce a ''descriptor'' that encodes the //properties// of the buffer, most notably its size, possibly also a handler function &rarr; BufferMetadata
+* yet we also include a link to a ''backing service'' -- which can be configured to suit the purpose of the processing
+** the standard case of a BufferProvider is, well, to provide buffers
+** yet, at various places in the Render Node network, these buffers are linked to a //frame cache//
+** furthermore, at the source side, buffers must be filled by file-I/O operations
+** while at the output side, buffers participate in a precisely timed protocol for media output.
 
-!sanity checks
-there are only limited sanity checks, and they can be expected to be optimised away for production builds.
-Basically the client is responsible for sane buffer access.
-
+!!!performance considerations +While the backing service remains opaque, it should be noted that accessing this service can be performance critical. Any »service« always carries some element of central management -- and accessing a central coordinator can be a dangerous task when operating within a highly concurrent environment. Careful consideration is necessary regarding the location of //state// embedded into such a service: as long as some call is just propagated through layers of API functions, the effects remain confined into the current call stack. At some point however, long-lived state must be accessed (otherwise the service would be devoid of any meaning). + +At this point, a separation is introduced into the //local coordinator// (which is implemented as a thread-local object) and the //central hub// of the service. As far as possible, the communication with the latter should be implemented as ''asynchronous messaging'' + +!!!sanity checks +There are only limited sanity checks, and they can be expected to be optimised away for production builds. +The render processing is assumed to proceed along preconfigured processing pipelines, which are instantiated from a »blueprint« -- and the [[Builder]] is responsible to arrange this calculation prototype in such a way, that only valid data accesses are possible. The processing functions and the buffer descriptors are preconfigured to match up.
-
+
Buffers are used to hold the media data for processing and output. Within the Lumiera RenderEngine and [[Player]] subsystem, we use some common concepts to handle the access and allocation of working buffers. Yet this doesn't imply having only one central authority in charge of every buffer -- such an approach wouldn't be possible (due to collaboration with external systems) and wouldn't be desirable either. Rather, there are some common basic usage //patterns// -- and there are some core interfaces used throughout the organisation of the rendering process.
 
 Mostly, the //client code,// i.e. code in need of using buffers, can access some BufferProvider, thereby delegating the actual buffer management. This binds the client to adhere to kind of a //buffer access protocol,// comprised of the ''announcing'', ''locking'', optionally ''attaching'' and finally the ''releasing'' steps. The actual implementation backing these operations is a matter of configuration, which will be setup as part of the ''build process'', when the Render Node network is created.
@@ -1243,7 +1255,7 @@ Mostly, the //client code,// i.e. code in need of using buffers, can access some
 
 !primary implementations
 ;memory pool
-:in all those situations, where we just need a working buffer for some time, a dedicated custom allocator will be used within the Render Engine. Access to this allocator from the actual worker threads is another, quite advanced topic, since we can not effort to contact a central manager component for each buffer access.
+:in all those situations, where we just need a working buffer for some time, a dedicated custom allocator will be used within the Render Engine. Access to this allocator from the actual worker threads is another, quite advanced topic, since we can not effort to contact a central manager component for each buffer access. Within each worker thread, a small //local allocation pool// is used, exchanging allocations with the central {{{EngineBufferManager}}} through lock-free queues.
 ;frame cache
 :whenever a calculated result may be of further interest, beyond the immediate need triggering the calculation, it might be eligible for caching.
 :The Lumiera ''frame cache'' is a special BufferProvider, maintaining a larger pool of buffers which can be pinned and kept around for some time,
@@ -1271,8 +1283,8 @@ Beyond that, it can be necessary to associate at least a state flag with //indiv
 __Note__: while the API to access this service is uniform, conceptually there is a difference between just using the (shared) type information and associating individual metadata, like the buffer state. Type-~IDs, once allocated, will never be discarded (within the lifetime of an Lumiera application instance -- while buffer associations, and  further individual metadata //will be discarded,// when releasing the corresponding buffer. According to the ''prototype pattern'', individual metadata is treated as a one-way-off specialisation.
 
-
-
Analysis of requirement shows that direct access to the buffers holding actual media data is not necessary -- at leas within the entire implementation of the Render Nodes Network. This finding is based on the assumption, that Lumiera does not perform any media processing on its own; all processing is delegated to external libraries. When taking this approach, buffers can be reduced to just some entity to be //managed,// i.e. "allocated", "locked" and "released"; the //actual meaning of these operations can be left to the implementation.// So the code within the render engine just pushes around ''smart-ptr like handles''. These [[buffer handles|BuffHandle]] act as a front-end, being created by and are linked to a {{{BufferProvider}}} implementation. There is no need to manage the lifecycle of buffers automatically, because the use of buffers is embedded into the render calculation cycle, which follows a strict call scheme, which ensures that all buffers are provided before invoking an operation and that buffers are released afterwards. Buffer management can thus be reduced to the following primitive operations...
+
+
In-depth analysis of requirements indicates that direct access to the buffers holding actual media data is not necessary -- at least //within the implementation// of the Render Nodes Network. This finding is based on the assumption that Lumiera does not perform any media processing on its own; all processing is delegated to external libraries. When taking this approach, buffers can be reduced to just some entity to be //managed,// i.e. "allocated", "locked" and "released"; the //actual meaning of these operations can be left to the implementation.// So access for the code within the render engine can be reduced to exchanging //light-weight handles//. These [[buffer handles|BuffHandle]] act as a front-end, being created by and are linked to a {{{BufferProvider}}} implementation. There is no need to manage the lifecycle of buffers automatically, because the use of buffers is embedded into the render calculation cycle, which follows a strict call scheme, which ensures that all buffers are provided before invoking an operation and that buffers are released afterwards. Buffer management can thus be reduced to the following primitive operations...
 * declaring the ''type'', i.e. the size of a buffer, and optionally an object to construct into the buffer storage
 * (optionally) an ''announcement'' that a given amount of buffers will be required soon
 * the next step is to ensure the necessary buffers are available, by obtaining (''locking'') a buffer handle
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index a6151fee5..537eb8dc3 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -143764,6 +143764,10 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
 
 
 
+
+
+
+
 
 
 
@@ -143863,7 +143867,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
   
 
 
-
+
 
   
   
@@ -143876,7 +143880,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
   
   
     

- ...wodurch die Worker-Threads diese Aufgabe probabilistisch mit erledigen: wannimmer ein Worker eine Allokation angefordert hat, diese aber noch nicht zugestellt wurde, macht er mit globalem Lock eine Runde der Allokations-Arbeit + ...wodurch die Worker-Threads diese Aufgabe probabilistisch mit erledigen: wann immer ein Worker eine Allokation angefordert hat, diese aber noch nicht zugestellt wurde, macht er mit globalem Lock eine Runde der Allokations-Arbeit

@@ -144066,7 +144070,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -144137,7 +144141,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -146314,7 +146318,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -146359,7 +146364,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -146621,7 +146626,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -146758,9 +146763,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - + + + @@ -146769,9 +146774,12 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + - + + @@ -146811,7 +146819,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -146854,12 +146862,13 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

+ - + @@ -146912,9 +146921,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -146972,7 +146981,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -147038,7 +147047,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -147094,7 +147103,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -147681,7 +147690,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -147858,8 +147867,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -147943,7 +147953,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148014,7 +148024,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148041,7 +148051,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148357,8 +148367,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -148406,11 +148416,11 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + @@ -148423,7 +148433,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148517,7 +148527,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -148532,7 +148543,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148556,7 +148567,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -148842,14 +148853,14 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + - + @@ -148860,15 +148871,16 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + - + @@ -148908,8 +148920,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) etwas anderes ist an der Stelle auch relativ unwahrscheinlich: die BASE_BUFFSIZ steckt ja eben grade statisch in der jeweilgen Thread-Klasse. Das würde nur abweichen, wenn man im gleichen Testfall mit mehreren verschiedenen Thread-Klassen operiert

- - + @@ -148921,8 +148932,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) lookup liefert nicht Key::INVALID

- - +
@@ -148941,7 +148951,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)
- + @@ -149018,8 +149028,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + @@ -149031,9 +149041,85 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + + + + + + +

+ wird ersichtlich, wenn man die Verteilung auf Jobs bei gleicher Gesamtzahl variiert +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + +

+ ...was darauf hindeutet, daß sonstige Aktivitäten hier erheblich störend wirken. So z.B. das regelmäßige Polling vom Main-Thread (was im Debug-Mode auch relativ aufwendig ist, weil ich iterExplorer verwende). +

+

+ Zudem laufen mehrere Monster auf meinem Rechner, die allesamt einen Threadpool haben und auch normalerweise keine Ruhe geben (Firefox, Thunderbird, Eclipse, GTK/XFCE) +

+ +
+
+ + + + +

+ es ist aber überhaupt nicht klar wo der Overhead herkommt +

+ +
+ + + + + + + + +

+ ...bedingt durch die Randomisierung, kann es nämlich durchaus sein, daß eine gewisse Teil-Kapazität permanent hin- und herschwappt; diese würde aktuell dann jeweils de-alloziert und kurz darauf wieder neu alloziert. +

+ +
+
+ + + + +

+ ...leider würde das eine spezielle Instrumentierung des Test-Subjects voraussetzen, und vermutlich auch eine genauere Steuerung des Verhaltens (die Randomisierung macht die Verhältnisse schwer abschätzbar). Erst damit ließe sich entscheiden, wie viel zusätzliche Zeit denn tatsächlich verbraucht wird. +

+ +
+
+
@@ -149044,10 +149130,18 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - + + + + + + + + + @@ -149067,7 +149161,8 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -149085,6 +149180,17 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + +

+ ...dabei muß aber nochmal betont werden, daß es keinerlei Bezugspunkt gibt, denn ich habe nicht »naiv« angefangen; es könnte nämlich durchaus sein, daß sich eine naive Implementierung noch viel schlechter schlägt, und dann schwer zu verbessern ist — es könnte aber auch sein, daß dieses System irrsinnig overengineered ist, und letztlich nicht gut auf Performance getuned werden kann +

+ +
+ +