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:
Matthew Parkinson
2020-02-21 11:34:39 +00:00
parent 8723ae2443
commit 813367286e
4 changed files with 161 additions and 98 deletions

View File

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