Make Lazy Decomit asynchronous
On platforms that support low-memory notifications register callbacks that perform lazy decommit. This allows idle processes to return memory to the OS. Without incurring the cost of constantly committing and decommitting memory. Code review and CI changes * Fixed test to use a template to make constexpr magic work * Factored out basic notification mechanism so can be reused on other platforms.
This commit is contained in:
@@ -55,7 +55,7 @@ namespace snmalloc
|
||||
// global state of the allocator. This is currently stored in the memory
|
||||
// provider, so we add this in.
|
||||
template<class PAL>
|
||||
class MemoryProviderStateMixin : public PAL
|
||||
class MemoryProviderStateMixin : public PalNotificationObject, public PAL
|
||||
{
|
||||
/**
|
||||
* Flag to protect the bump allocator
|
||||
@@ -72,11 +72,6 @@ namespace snmalloc
|
||||
**/
|
||||
size_t remaining = 0;
|
||||
|
||||
/**
|
||||
* The last time we saw a low memory notification.
|
||||
*/
|
||||
std::atomic<uint64_t> last_low_memory_epoch = 0;
|
||||
|
||||
/**
|
||||
* Simple flag for checking if another instance of lazy-decommit is
|
||||
* running
|
||||
@@ -105,6 +100,13 @@ namespace snmalloc
|
||||
// memcpy is safe as this is entirely single threaded.
|
||||
memcpy(allocated, &local, sizeof(MemoryProviderStateMixin<PAL>));
|
||||
|
||||
// Register this allocator for low-memory call-backs
|
||||
if constexpr (pal_supports<LowMemoryNotification, PAL>)
|
||||
{
|
||||
allocated->PalNotificationObject::pal_notify = &(allocated->process);
|
||||
PAL::register_for_low_memory_callback(allocated);
|
||||
}
|
||||
|
||||
return allocated;
|
||||
}
|
||||
|
||||
@@ -183,6 +185,16 @@ namespace snmalloc
|
||||
large_stack[large_class].push(reinterpret_cast<Largeslab*>(p));
|
||||
}
|
||||
|
||||
/***
|
||||
* Method for callback object to perform lazy decommit.
|
||||
**/
|
||||
static void process(PalNotificationObject* p)
|
||||
{
|
||||
// Unsafe downcast here. Don't want vtable and RTTI.
|
||||
auto self = reinterpret_cast<MemoryProviderStateMixin<PAL>*>(p);
|
||||
self->lazy_decommit();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Primitive allocator for structure that are required before
|
||||
@@ -235,24 +247,6 @@ namespace snmalloc
|
||||
return new (p) T(std::forward<Args...>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of low memory notifications that have been received
|
||||
* (over the lifetime of this process). If the underlying system does not
|
||||
* support low memory notifications, this will return 0.
|
||||
*/
|
||||
SNMALLOC_FAST_PATH
|
||||
uint64_t low_memory_epoch()
|
||||
{
|
||||
if constexpr (pal_supports<LowMemoryNotification, PAL>)
|
||||
{
|
||||
return PAL::low_memory_epoch();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<bool committed>
|
||||
void* reserve(size_t large_class) noexcept
|
||||
{
|
||||
@@ -316,41 +310,6 @@ namespace snmalloc
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH void lazy_decommit_if_needed()
|
||||
{
|
||||
#ifdef TEST_LAZY_DECOMMIT
|
||||
static_assert(
|
||||
TEST_LAZY_DECOMMIT > 0,
|
||||
"TEST_LAZY_DECOMMIT must be a positive integer value.");
|
||||
static std::atomic<uint64_t> counter;
|
||||
auto c = counter++;
|
||||
if (c % TEST_LAZY_DECOMMIT == 0)
|
||||
{
|
||||
lazy_decommit();
|
||||
}
|
||||
#else
|
||||
if constexpr (decommit_strategy == DecommitSuperLazy)
|
||||
{
|
||||
auto new_epoch = low_memory_epoch();
|
||||
auto old_epoch = last_low_memory_epoch.load(std::memory_order_acquire);
|
||||
if (new_epoch > old_epoch)
|
||||
{
|
||||
// Try to update the epoch to the value that we've seen. If
|
||||
// another thread has seen a newer epoch than us (or done the same
|
||||
// update) let them win.
|
||||
do
|
||||
{
|
||||
if (last_low_memory_epoch.compare_exchange_strong(
|
||||
old_epoch, new_epoch))
|
||||
{
|
||||
lazy_decommit();
|
||||
}
|
||||
} while (old_epoch < new_epoch);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
|
||||
@@ -381,7 +340,6 @@ namespace snmalloc
|
||||
size = rsize;
|
||||
|
||||
void* p = memory_provider.large_stack[large_class].pop();
|
||||
memory_provider.lazy_decommit_if_needed();
|
||||
|
||||
if (p == nullptr)
|
||||
{
|
||||
@@ -392,8 +350,7 @@ namespace snmalloc
|
||||
{
|
||||
stats.superslab_pop();
|
||||
|
||||
// Cross-reference alloc.h's large_dealloc decommitment condition
|
||||
// and lazy_decommit_if_needed.
|
||||
// Cross-reference alloc.h's large_dealloc decommitment condition.
|
||||
bool decommitted =
|
||||
((decommit_strategy == DecommitSuperLazy) &&
|
||||
(static_cast<Baseslab*>(p)->get_kind() == Decommitted)) ||
|
||||
@@ -449,7 +406,6 @@ namespace snmalloc
|
||||
|
||||
stats.superslab_push();
|
||||
memory_provider.large_stack[large_class].push(static_cast<Largeslab*>(p));
|
||||
memory_provider.lazy_decommit_if_needed();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user