diff --git a/research/try.cpp b/research/try.cpp index d722f412c..c6443a90f 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -53,6 +53,7 @@ typedef unsigned int uint; #include "lib/util.hpp" + #define SHOW_TYPE(_TY_) \ cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() < +#include + namespace { + constexpr size_t NUM_MEASUREMENTS = 10000000; + constexpr double SCALE = 1e6; // Results are in ยต sec + } + + + /** perform a multithreaded microbenchmark. + * This function fires up a number of threads + * and invokes the given test subject repeatedly. + * @tparam number of threads to run in parallel + * @param subject `void(void)` function to be timed + * @return the averaged invocation time in _mircroseconds_ + * @remarks - the subject function will be _copied_ into each thread + * - so `nThreads` copies of this function will run in parallel + * - consider locking if this function accesses a shared closure. + * - if you pass a lambda, it is eligible for inlining followed + * by loop optimisation -- be sure to include some action, like + * e.g. accessing a volatile variable, to prevent the compiler + * from optimising it away entirely. + */ + template + double + microbenchmark(FUN const& subject) + { + using backend::ThreadJoinable; + using std::chrono::system_clock; + + using Dur = std::chrono::duration; + + struct Thread + : ThreadJoinable + { + Thread(FUN const& subject) + : ThreadJoinable("Micro-Benchmark" + ,[subject, this]() // local copy of the test-subject-Functor + { + syncPoint(); // block until all threads are ready + auto start = system_clock::now(); + for (size_t i=0; i < NUM_MEASUREMENTS; ++i) + subject(); + duration = system_clock::now () - start; + }) + { } + /** measured time within thread */ + Dur duration{}; + }; + + std::vector threads; + threads.reserve(nThreads); + for (size_t n=0; n::useSingleton ([&] { return "long{rand() % 100}"; }); // DependInject::Local dummy ([&]{ return new long{rand() % 100}; }); - cout << "rrrrrr.."<< Depend{}() < ([&]() + { + //volatile int dummy =0; + //dummy == 0; + //++dummy; + blackHole == 0; + //++blackHole; + }) + << endl; + cout << "........"<< blackHole/8< -
+
//Access point to dependencies by-name.//
-In the Lumiera code base, we refrain from building or using a full-blown Dependency Injection Container. A lot of FUD has been spread regarding Dependency Injection and Singletons, to the point that a majority of developers confuses and conflates the ~Inversion-of-Control principle (which is essential) with the use of a ~DI-Container. Today, you can not even mention the word "Singleton" without everyone yelling out "Evil! Evil!" -- while most of these people just feel comfortable living in the annotation hell.
+In the Lumiera code base, we refrain from building or using a full-blown Dependency Injection Container. A lot of FUD has been spread regarding Dependency Injection and Singletons, to the point that a majority of developers confuses and conflates the ~Inversion-of-Control principle (which is essential) with the use of a ~DI-Container. Today, you can not even mention the word "Singleton" without everyone yelling out "Evil! Evil!" -- while most of these people just feel comfortable living in the metadata hell.
 
-Not Singletons as such are problematic -- rather, the coupling of the Singleton class itself with the instantiation and lifecycle mechanism is what creates the problems. In C++ these problems can be mitigated by use of a generic //Singleton Factory// -- which can be augmented into a DependencyFactory for those rare cases where we actually need more instance and lifecycle management beyond lazy initialisation. Client code indicates the dependence on some other service by planting an instance of that Dependency Factory (for Lumiera this is {{{lib::Depend<TY>}}}) and remain unaware if the instance is created lazily in singleton style (which is the default) or has been reconfigured to expose a service instance explicitly created by some subsystem lifecycle.
+Not Singletons as such are problematic -- rather, the coupling of the Singleton class itself with the instantiation and lifecycle mechanism is what creates the problems. In C++ these problems can be mitigated by use of a generic //Singleton Factory// -- which can be augmented into a DependencyFactory for those rare cases where we actually need more instance and lifecycle management beyond lazy initialisation. Client code indicates the dependence on some other service by planting an instance of that Dependency Factory (for Lumiera this is {{{lib::Depend<TY>}}}) and remain unaware if the instance is created lazily in singleton style (which is the default) or has been reconfigured to expose a service instance explicitly created by some subsystem lifecycle. The //essence of a "dependency"// of this kind is that we ''access a service //by name//''. And this service name or service ID is in our case a //type name.//
 
 !Requirements
 Our DependencyFactory satisfies the following requirements
 * client code is able to access some service //by-name// -- where the name is actually the //type name// of the service interface.
 * client code remains agnostic with regard to the lifecycle or backing context of the service it relies on
-* in the simplest (and most prominent case), //nothing// has to be done at all by anyone to manage that lifecycle. By default, the DependencyFactory creates a singleton instance lazily in static memory on demand and ensures thread-safe initialisation and access.
+* in the simplest (and most prominent case), //nothing// has to be done at all by anyone to manage that lifecycle.<br/>By default, the DependencyFactory creates a singleton instance lazily (heap allocated) on demand and ensures thread-safe initialisation and access.
 * we establish a policy to ''disallow any significant functionality during application shutdown''. After leaving {{{main()}}}, only trivial dtors are invoked and possibly a few resource handles are dropped. No filesystem writes, no clean-up and reorganisation, not even any logging is allowed. For this reason, we established a [[Subsystem]] concept with explicit shutdown hooks, which are invoked beforehand.
 * the DependencyFactory can be re-configured for individual services (type names) to refer to an explicitly installed service instance. In those cases, access while the service is not available will raise an exception. There is a simple one-shot mechanism to reconfigure DependencyFactory and create a link to an actual service implementation, including automatic deregistration.
 
@@ -1963,9 +1963,24 @@ Deliberately, we do not enforce global consistency statically (since that would
 :the next access will create a (non singleton) {{{SubBlah}}} instance in heap memory and return a {{{Blah&}}}
 :the generated object again acts as lifecycle handle and smart-ptr to access the {{{SubBlah}}} instance like {{{mock->doItSpecial()}}}
 :when this handle goes out of scope, the original configuration of the dependency factory is restored
+;custom constructors
+:both the subclass singleton configuration and the test mock support optionally accept a functor or lambda argument with signature {{{SubBlah*()}}}.
+:the contract is for this construction functor to return a heap allocated object, which will be owned and managed by the DependencyFactory.
+:especially this enables use of subclasses with non default ctor and / or binding to some additional hidden context.
+:please note //that this closure will be invoked later, on-demand.//
 
-We consider the usage pattern of dependencies a question of architecture rather -- such can not be solved by any mechanism on implementation level.
+We consider the usage pattern of dependencies a question of architecture rather -- such can not be solved by any mechanism at implementation level.
 For this reason, DependencyFactory prevents reconfiguration after use, but does nothing exceeding such basic sanity checks
+
+!!!Performance considerations
+We acknowledge that such a dependency or service will be accessed frequently and even from rather performance critical parts of the application. We have to optimise for low overhead on access, while initialisation happens only once and can be arbitrarily expensive. At which point precisely initialisation happens is a question of architecture -- lazy initialisation can be used to avoid expensive setup of rarely used services, or it can be employed to simplify the bootstrap of complex subsystems, or to break service dependency cycles. All of this builds on the assumption that the global application structure is fixed and finite and well-known -- we assume we are in full control about when and how parts of the application start and stop working.
+
+Our requirements on (optional) reconfigurability have some impact on the implementation technique though, since we need access to the instance pointer for individual service types. This basically rules out //Meyers Singleton// -- and so the adequate implementation technique for our usage pattern is //Double Checked Locking.// In the past, there was much debate about DCL being broken -- which indeed was true when //assuming full portability and arbitrary target platform.// Since our focus is primarily on ~PC-with-Linux systems, this argument seems rather theoretical though, since the x86/64 platform is known to employ rather strong memory and cache coherency constraints. With the advent of ARM systems, the situation has changed however. Anyway, since C++11 there is a portable solution for writing a correct DCL implementation, based on {{{std::atomic}}}.
+
+To give some idea of the rough proportions of performance impact, in 2018 we conducted some micro benchmarks (using a 8 core AMD 64bit processor running Debian/Jessie building with GCC 4.9)
+The following table lists averaged results in relative numbers
+| !Access Technique |>| !development |>| !optimised |
+|~| single threaded|multithreaded | single threaded|multithreaded |
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 060263325..549d608b5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -26468,6 +26468,148 @@ + + + + + + + + + + + + + + + + + + + + + +

+ man hätte genausogut std::future und std::async verwenden können. +

+

+ Vorteil von unseren Framework: +

+
    +
  • + wir haben es schon, und wir werden es verwenden, wegen den Thradpools +
  • +
  • + man baut ein Objekt für einen Thread. Das ist explizit und sauber +
  • +
  • + wir haben eine eingebaute Barriere und können unseren Objekt-Monitor nutzen +
  • +
+ + +
+ +
+ + + + + + + + + + + + +

+ habe einen usleep(1000) getimed +

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

+ daher messen wir die Loop als Ganzes. +

+

+ Es gibt daher keine Möglichkeit, den Loop-Overhead selber zu messen. +

+

+ Er sollte sich aber bei einer Wiederholung im Millionenbereich gut amortisieren +

+

+ +

+

+ Außerdem ist ja auch noch der Aufruf des Funktors mit im Spiel, wenngleich der auch typischerweise geinlined wird +

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

+ volatile Variable außen, im Aufrufkontext +

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

+ ...was sehr schön beweist, +

+

+ daß x86_64 tatsächlich cache-kohärent ist +

+ + +
+
+
+
+
+