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:
Fischlurch 2023-11-24 15:43:26 +01:00
parent 98078b9bb6
commit 8de3fe21bb
4 changed files with 93 additions and 60 deletions

View file

@ -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>());
}
};

View file

@ -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;
}

View file

@ -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));
}

View file

@ -97285,9 +97285,15 @@ Date:&#160;&#160;&#160;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 &#xfc;ber diesen um Funktor(Referenz) zu bekommen">
<icon BUILTIN="button_ok"/>
@ -97315,29 +97321,39 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1700794763222" ID="ID_122093052" MODIFIED="1700794788339" TEXT="best&#xe4;tigt: &#x3bb;-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>&#160;mit 1 &#187;Slot&#171;
<u>Ergebnis</u>: funktioniert <b>nicht</b>&#160;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 &#x27fc; Stack-Addresse"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794830533" ID="ID_871196012" MODIFIED="1700794849742" TEXT="wenn Lambda shared_ptr captured &#x27fc; Heap-Addresse"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700794792578" ID="ID_1451884191" MODIFIED="1700829893535" TEXT="wenn Lambda einfachen ptr captured &#x27fc; 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 &#x27fc; Heap-Addresse">
<icon BUILTIN="info"/>
</node>
<node CREATED="1700829830508" ID="ID_1902079754" MODIFIED="1700829882897" TEXT="wenn Lambda char[16] captured &#x27fc; Stack-Addresse ">
<icon BUILTIN="info"/>
</node>
<node CREATED="1700829855310" ID="ID_697686576" MODIFIED="1700829882896" TEXT="wenn Lambda char[24] captured &#x27fc; Heap-Addresse ">
<icon BUILTIN="info"/>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1700794933479" ID="ID_1065100472" MODIFIED="1700794949477" TEXT="&#x27f9; 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&#xdf; 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&#xdf; 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">