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

@@ -10,8 +10,8 @@ namespace snmalloc
{
/**
* This PAL supports low memory notifications. It must implement a
* `low_memory_epoch` method that returns a `uint64_t` of the number of
* times that a low-memory notification has been raised and an
* `register_for_low_memory_callback` method that allows callbacks to be
* registered when the platform detects low-memory and an
* `expensive_low_memory_check()` method that returns a `bool` indicating
* whether low memory conditions are still in effect.
*/
@@ -53,4 +53,63 @@ namespace snmalloc
* Default Tag ID for the Apple class
*/
static const int PALAnonDefaultID = 241;
/**
* This struct is used to represent callbacks for notification from the
* platform. It contains a next pointer as client is responsible for
* allocation as we cannot assume an allocator at this point.
**/
struct PalNotificationObject
{
std::atomic<PalNotificationObject*> pal_next;
void (*pal_notify)(PalNotificationObject* self);
};
/***
* Wrapper for managing notifications for PAL events
**/
class PalNotifier
{
/**
* List of callbacks to notify
**/
std::atomic<PalNotificationObject*> callbacks = nullptr;
public:
/**
* Register a callback object to be notified
*
* The object should never be deallocated by the client after calling
* this.
**/
void register_notification(PalNotificationObject* callback)
{
callback->pal_next = nullptr;
auto prev = &callbacks;
auto curr = prev->load();
do
{
while (curr != nullptr)
{
prev = &(curr->pal_next);
curr = prev->load();
}
} while (!prev->compare_exchange_weak(curr, callback));
}
/**
* Calls the pal_notify of all the registered objects.
**/
void notify_all()
{
PalNotificationObject* curr = callbacks;
while (curr != nullptr)
{
curr->pal_notify(curr);
curr = curr->pal_next;
}
}
};
} // namespace snmalloc

View File

@@ -20,23 +20,25 @@ namespace snmalloc
{
class PALWindows
{
/**
* The number of times that the memory pressure notification has fired.
*/
static inline std::atomic<uint64_t> pressure_epoch;
/**
* A flag indicating that we have tried to register for low-memory
* notifications.
*/
static inline std::atomic<bool> registered_for_notifications;
static inline HANDLE lowMemoryObject;
/**
* List of callbacks for low-memory notification
**/
static inline PalNotifier low_memory_callbacks;
/**
* Callback, used when the system delivers a low-memory notification. This
* simply increments an atomic counter each time the notification is raised.
* calls all the handlers registered with the PAL.
*/
static void CALLBACK low_memory(_In_ PVOID, _In_ BOOLEAN)
{
pressure_epoch++;
low_memory_callbacks.notify_all();
}
public:
@@ -76,17 +78,6 @@ namespace snmalloc
# endif
;
/**
* Counter values for the number of times that a low-pressure notification
* has been delivered. Callers should compare this with a previous value
* to see if the low memory state has been triggered since they last
* checked.
*/
uint64_t low_memory_epoch()
{
return pressure_epoch.load(std::memory_order_acquire);
}
/**
* Check whether the low memory state is still in effect. This is an
* expensive operation and should not be on any fast paths.
@@ -98,6 +89,17 @@ namespace snmalloc
return result;
}
/**
* Register callback object for low-memory notifications.
* Client is responsible for allocation, and ensuring the object is live
* for the duration of the program.
**/
static void
register_for_low_memory_callback(PalNotificationObject* callback)
{
low_memory_callbacks.register_notification(callback);
}
static void error(const char* const str)
{
puts(str);