Chain-Load: detect small-object optimisation
- Helper function to find out of two objects are located "close to each other" -- which can be used as heuristics to distinguish heap vs. stack storage - further investigation shows that libstdc++ applies the small-object optimisation for functor up to »two slots« in size -- but only if the copy-ctor is trivial. Thus a lambda capturing a shared_ptr by value will *always* be maintained in heap storage (and LazyInit must be redesigned accordingly)... - the verify_inlineStorage() unit test will now trigger if some implementation does not apply small-object optimisation under these minimal assumptions
This commit is contained in:
parent
98078b9bb6
commit
8de3fe21bb
4 changed files with 93 additions and 60 deletions
|
|
@ -105,18 +105,15 @@ namespace lib {
|
|||
template<class SIG>
|
||||
class TrojanFun
|
||||
{
|
||||
template<class DEL>
|
||||
using Manager = std::shared_ptr<DEL>;
|
||||
|
||||
template<class DEL, typename RET, typename... ARGS>
|
||||
static auto
|
||||
buildTrapActivator (Manager<DEL>&& managedDelegate, _Fun<RET(ARGS...)>)
|
||||
buildTrapActivator (DEL* delegate, _Fun<RET(ARGS...)>)
|
||||
{
|
||||
return [chain = move(managedDelegate)]
|
||||
return [delegate]
|
||||
(ARGS ...args) -> RET
|
||||
{
|
||||
auto currLocation = &chain;
|
||||
auto& functor = (*chain) (currLocation);
|
||||
auto currLocation = &delegate;
|
||||
auto& functor = (*delegate) (currLocation);
|
||||
//
|
||||
return functor (forward<ARGS> (args)...);
|
||||
};
|
||||
|
|
@ -146,7 +143,7 @@ namespace lib {
|
|||
|
||||
REQUIRE (delegate);
|
||||
|
||||
return buildTrapActivator (Manager<DEL>{delegate}, _Fun<SIG>());
|
||||
return buildTrapActivator (delegate, _Fun<SIG>());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@ namespace util {
|
|||
{
|
||||
return static_cast<const void*> (std::addressof(x));
|
||||
}
|
||||
|
||||
template<class X>
|
||||
inline const void*
|
||||
getAddr (X* x)
|
||||
|
|
@ -365,6 +364,34 @@ namespace util {
|
|||
return static_cast<const void*> (x);
|
||||
}
|
||||
|
||||
/** the addressable memory »slot« — platform dependent */
|
||||
template<typename X>
|
||||
inline size_t
|
||||
slotNr (X const& x)
|
||||
{
|
||||
return reinterpret_cast<size_t> (std::addressof(x)) / sizeof(size_t);
|
||||
}
|
||||
template<typename X>
|
||||
inline size_t
|
||||
slotNr (X const* x)
|
||||
{
|
||||
return reinterpret_cast<size_t> (x) / sizeof(size_t);;
|
||||
}
|
||||
|
||||
|
||||
/** determine heuristically if two objects
|
||||
* are located „close to each other“ in memory.
|
||||
* @remark can be used to find out about heap vs. stack allocation
|
||||
*/
|
||||
template<typename A, typename B>
|
||||
inline bool
|
||||
isCloseBy (A&& a, B&& b, size_t consideredNearby =50)
|
||||
{
|
||||
size_t loc1 = slotNr (std::forward<A> (a));
|
||||
size_t loc2 = slotNr (std::forward<B> (b));
|
||||
size_t dist = loc2 > loc1? loc2-loc1:loc1-loc2;
|
||||
return dist < consideredNearby;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#include "lib/lazy-init.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
//#include "lib/test/test-helper.hpp"
|
||||
#include "lib/test/testdummy.hpp"
|
||||
//#include "lib/test/testdummy.hpp"
|
||||
#include "lib/test/diagnostic-output.hpp" /////////////////////TODO TODOH
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ namespace test{
|
|||
// now (finally) build the »trap function«,
|
||||
// taking ownership of the heap-allocated delegate copy
|
||||
auto trojanLambda = TrojanFun<Sig>::generateTrap (delP);
|
||||
CHECK (sizeof(trojanLambda) == 2*sizeof(size_t));
|
||||
CHECK (sizeof(trojanLambda) == sizeof(size_t));
|
||||
|
||||
// on invocation...
|
||||
// - it captures its current location
|
||||
|
|
@ -162,50 +162,43 @@ namespace test{
|
|||
|
||||
|
||||
/** @test verify that std::function indeed stores a simple functor inline
|
||||
* @remark The implementation of LazyInit relies crucially on a known optimisation
|
||||
* in the standard library ─ which unfortunately is not guaranteed by the standard:
|
||||
* Typically, std::function will apply _small object optimisation_ to place a very
|
||||
* small functor directly into the wrapper, if the payload has a trivial copy-ctor.
|
||||
* Libstdc++ is known to be rather restrictive, other implementations trade increased
|
||||
* storage size of std::function against more optimisation possibilities.
|
||||
* LazyInit exploits this optimisation to „spy“ about the current object location,
|
||||
* to allow executing the lazy initialisation on first use, without further help
|
||||
* by client code. This trickery seems to be the only way, since λ-capture by reference
|
||||
* is broken after copying or moving the host object (which is required for DSL use).
|
||||
* In case this turns out to be fragile, LazyInit should become a "LateInit" and needs
|
||||
* help by the client or the user to trigger initialisation; alternatively the DSL
|
||||
* could be split off into a separate builder object distinct from RandomDraw.
|
||||
*/
|
||||
void
|
||||
verify_inlineStorage()
|
||||
{
|
||||
Dummy::checksum() = 0;
|
||||
using DummyManager = std::shared_ptr<Dummy>;
|
||||
// char payload[24];// ◁─────────────────────────────── use this to make the test fail....
|
||||
const char* payload = "Outer Space";
|
||||
auto lambda = [payload]{ return RawAddr(&payload); };
|
||||
|
||||
DummyManager dummy{new Dummy};
|
||||
long currSum = Dummy::checksum();
|
||||
CHECK (currSum > 0);
|
||||
CHECK (currSum == dummy->getVal());
|
||||
CHECK (1 == dummy.use_count());
|
||||
{
|
||||
// --- nested Scope ---
|
||||
auto lambda = [dummy]{ return &dummy; };
|
||||
CHECK (2 == dummy.use_count());
|
||||
CHECK (currSum == Dummy::checksum());
|
||||
|
||||
RawAddr location = lambda();
|
||||
CHECK (location == &lambda);
|
||||
|
||||
std::function funWrap{lambda};
|
||||
CHECK (funWrap);
|
||||
CHECK (not isSameObject (funWrap, lambda));
|
||||
CHECK (3 == dummy.use_count());
|
||||
CHECK (currSum == Dummy::checksum());
|
||||
|
||||
SHOW_EXPR(location)
|
||||
location = funWrap();
|
||||
SHOW_EXPR(location)
|
||||
SHOW_EXPR(RawAddr(&funWrap))
|
||||
SHOW_EXPR(RawAddr(dummy.get()))
|
||||
|
||||
std::function fuckWrap{[location]{ return &location; }};
|
||||
CHECK (fuckWrap);
|
||||
SHOW_EXPR(RawAddr(&location))
|
||||
SHOW_EXPR(RawAddr(fuckWrap()))
|
||||
SHOW_EXPR(RawAddr(&fuckWrap))
|
||||
}
|
||||
CHECK (1 == dummy.use_count());
|
||||
CHECK (currSum == Dummy::checksum());
|
||||
dummy.reset();
|
||||
CHECK (0 == dummy.use_count());
|
||||
CHECK (0 == Dummy::checksum());
|
||||
RawAddr location = lambda();
|
||||
CHECK (location == &lambda);
|
||||
|
||||
std::function funWrap{lambda};
|
||||
CHECK (funWrap);
|
||||
CHECK (not isSameObject (funWrap, lambda));
|
||||
|
||||
location = funWrap();
|
||||
CHECK (util::isCloseBy (location, funWrap));
|
||||
// if »small object optimisation« was used,
|
||||
// the lambda will be copied directly into the std:function;
|
||||
// otherwise it will be heap allocated and this test fails.
|
||||
|
||||
// for context: these are considered "close by",
|
||||
// since both are sitting right here in the same stack frame
|
||||
CHECK (util::isCloseBy (funWrap, lambda));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -97285,9 +97285,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700786308827" ID="ID_627670298" MODIFIED="1700792001666" TEXT="der Trojanische-Funktor">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#5b280f" CREATED="1700786329960" ID="ID_579721558" MODIFIED="1700795318817" TEXT="bettet einen shared_ptr ein">
|
||||
<linktarget COLOR="#e64156" DESTINATION="ID_579721558" ENDARROW="Default" ENDINCLINATION="556;-38;" ID="Arrow_ID_1803799256" SOURCE="ID_1318522029" STARTARROW="None" STARTINCLINATION="-60;-142;"/>
|
||||
<node COLOR="#5b280f" CREATED="1700786329960" ID="ID_579721558" MODIFIED="1700829943723" TEXT="bettet einen shared_ptr ein">
|
||||
<linktarget COLOR="#e64156" DESTINATION="ID_579721558" ENDARROW="Default" ENDINCLINATION="581;-41;" ID="Arrow_ID_1803799256" SOURCE="ID_1318522029" STARTARROW="None" STARTINCLINATION="-98;-231;"/>
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
<node CREATED="1700829899760" HGAP="32" ID="ID_1908682930" MODIFIED="1700829932102" TEXT="liegt wohl am nicht-trivialen copy-ctor" VSHIFT="6">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1700829151354" ID="ID_1017319920" MODIFIED="1700829172371" TEXT="kann nur einen einfachen Pointer auf das Delegate halten">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1700786341382" ID="ID_1622424867" MODIFIED="1700792030724" TEXT="delegiert über diesen um Funktor(Referenz) zu bekommen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
|
|
@ -97315,29 +97321,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node COLOR="#338800" CREATED="1700794763222" ID="ID_122093052" MODIFIED="1700794788339" TEXT="bestätigt: λ-Addresse == Addresse des capture im Lambda">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e9ecac" COLOR="#ad0163" CREATED="1700794701860" ID="ID_1782883387" MODIFIED="1700794912605">
|
||||
<node BACKGROUND_COLOR="#e9ecac" COLOR="#ad0163" CREATED="1700794701860" ID="ID_1782883387" MODIFIED="1700829809186">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<u>Ergebnis</u>: funktioniert <b>nur</b> mit 1 »Slot«
|
||||
<u>Ergebnis</u>: funktioniert <b>nicht</b> mit shared_ptr
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794792578" ID="ID_1451884191" MODIFIED="1700794849741" TEXT="wenn Lambda einfachen ptr captured ⟼ Stack-Addresse"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794830533" ID="ID_871196012" MODIFIED="1700794849742" TEXT="wenn Lambda shared_ptr captured ⟼ Heap-Addresse"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794792578" ID="ID_1451884191" MODIFIED="1700829893535" TEXT="wenn Lambda einfachen ptr captured ⟼ Stack-Addresse">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794830533" ID="ID_871196012" MODIFIED="1700829893535" TEXT="wenn Lambda shared_ptr captured ⟼ Heap-Addresse">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node CREATED="1700829830508" ID="ID_1902079754" MODIFIED="1700829882897" TEXT="wenn Lambda char[16] captured ⟼ Stack-Addresse ">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node CREATED="1700829855310" ID="ID_697686576" MODIFIED="1700829882896" TEXT="wenn Lambda char[24] captured ⟼ Heap-Addresse ">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1700794933479" ID="ID_1065100472" MODIFIED="1700794949477" TEXT="⟹ Redesign">
|
||||
<icon BUILTIN="flag-pink"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700795056795" ID="ID_957544868" MODIFIED="1700795103642" TEXT="der Trojaner kann lediglich das Delegate aufrufen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1700795056795" ID="ID_957544868" MODIFIED="1700836667274" TEXT="der Trojaner kann lediglich das Delegate aufrufen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700794955820" ID="ID_1318522029" MODIFIED="1700795323054" TEXT="Storage-Management muß separat erfolgen">
|
||||
<arrowlink COLOR="#e64156" DESTINATION="ID_579721558" ENDARROW="Default" ENDINCLINATION="556;-38;" ID="Arrow_ID_1803799256" STARTARROW="None" STARTINCLINATION="-60;-142;"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700794955820" ID="ID_1318522029" MODIFIED="1700829943723" TEXT="Storage-Management muß separat erfolgen">
|
||||
<arrowlink COLOR="#e64156" DESTINATION="ID_579721558" ENDARROW="Default" ENDINCLINATION="581;-41;" ID="Arrow_ID_1803799256" STARTARROW="None" STARTINCLINATION="-98;-231;"/>
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700795105608" ID="ID_1474010761" MODIFIED="1700795128644" TEXT="bleibt nur der LazyInit-Mix-in selber">
|
||||
|
|
|
|||
Loading…
Reference in a new issue