Add a timer to the PAL
This adds a way to periodically pool the PAL to see if any timers have expired. Timers can be used to periodically provide callbacks to the rest of snmalloc.
This commit is contained in:
committed by
Matthew Parkinson
parent
c1062e629e
commit
20a114cb62
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "bits.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class FlagLock
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "flaglock.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <type_traits>
|
||||
|
||||
namespace snmalloc
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "pool.h"
|
||||
#include "remotecache.h"
|
||||
#include "sizeclasstable.h"
|
||||
#include "ticker.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
@@ -96,6 +97,11 @@ namespace snmalloc
|
||||
*/
|
||||
LocalCache* attached_cache;
|
||||
|
||||
/**
|
||||
* Ticker to query the clock regularly at a lower cost.
|
||||
*/
|
||||
Ticker<typename SharedStateHandle::Pal> ticker;
|
||||
|
||||
/**
|
||||
* The message queue needs to be accessible from other threads
|
||||
*
|
||||
@@ -405,6 +411,7 @@ namespace snmalloc
|
||||
std::cout << "Slab is woken up" << std::endl;
|
||||
#endif
|
||||
|
||||
ticker.check_tick();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -419,6 +426,7 @@ namespace snmalloc
|
||||
{
|
||||
dealloc_local_slabs(sizeclass);
|
||||
}
|
||||
ticker.check_tick();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -698,7 +706,8 @@ namespace snmalloc
|
||||
sl.insert(meta);
|
||||
}
|
||||
|
||||
return finish_alloc<zero_mem, SharedStateHandle>(p, sizeclass);
|
||||
auto r = finish_alloc<zero_mem, SharedStateHandle>(p, sizeclass);
|
||||
return ticker.check_tick(r);
|
||||
}
|
||||
return small_alloc_slow<zero_mem>(sizeclass, fast_free_list, rsize);
|
||||
}
|
||||
@@ -766,7 +775,8 @@ namespace snmalloc
|
||||
alloc_classes[sizeclass].available.insert(meta);
|
||||
}
|
||||
|
||||
return finish_alloc<zero_mem, SharedStateHandle>(p, sizeclass);
|
||||
auto r = finish_alloc<zero_mem, SharedStateHandle>(p, sizeclass);
|
||||
return ticker.check_tick(r);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
97
src/mem/ticker.h
Normal file
97
src/mem/ticker.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
/**
|
||||
* This class will attempt to call the PAL every 50ms to check the time.
|
||||
* If the caller of check_tick, does so more frequently, it will attempt
|
||||
* to back-off to only query the time, every n calls to check_tick, where
|
||||
* `n` adapts to the current frequency of calling.
|
||||
*
|
||||
* The aim is to reduce the time spent querying the time as this might be
|
||||
* an expensive operation if time has been virtualised.
|
||||
*/
|
||||
template<typename PAL>
|
||||
class Ticker
|
||||
{
|
||||
/**
|
||||
* Calls to check_tick required before the time is next queried
|
||||
*/
|
||||
uint64_t count_down = 1;
|
||||
|
||||
/**
|
||||
* Number of ticks next time we check the time.
|
||||
* That is,
|
||||
* counted - count_down
|
||||
* Is how many ticks, since last_epoch_ms was updated.
|
||||
*/
|
||||
uint64_t counted = 1;
|
||||
|
||||
/**
|
||||
* Last time we queried the clock.
|
||||
*/
|
||||
uint64_t last_query_ms = 0;
|
||||
|
||||
/**
|
||||
* Slow path that actually queries clock and sets up
|
||||
* how many calls for the next time we hit the slow path.
|
||||
*/
|
||||
template<typename T = void*>
|
||||
SNMALLOC_SLOW_PATH T check_tick_slow(T p = nullptr)
|
||||
{
|
||||
uint64_t now_ms = PAL::time_in_ms();
|
||||
|
||||
// Set up clock.
|
||||
if (last_query_ms == 0)
|
||||
{
|
||||
last_query_ms = now_ms;
|
||||
count_down = 1;
|
||||
counted = 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
uint64_t duration_ms = now_ms - last_query_ms;
|
||||
last_query_ms = now_ms;
|
||||
|
||||
// Check is below clock resolution
|
||||
if (duration_ms == 0)
|
||||
{
|
||||
// Exponential back off
|
||||
count_down = counted;
|
||||
counted *= 2;
|
||||
return p;
|
||||
}
|
||||
|
||||
constexpr size_t deadline_in_ms = 50;
|
||||
|
||||
// Estimate number of ticks to get to the new deadline, based on the
|
||||
// current interval
|
||||
auto new_deadline_in_ticks =
|
||||
((1 + counted) * deadline_in_ms) / duration_ms;
|
||||
|
||||
counted = new_deadline_in_ticks;
|
||||
count_down = new_deadline_in_ticks;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T = void*>
|
||||
SNMALLOC_FAST_PATH T check_tick(T p = nullptr)
|
||||
{
|
||||
// Check before decrement, so that later calcations can use
|
||||
// count_down == 0 for check on the next call.
|
||||
// This is used if the ticks are way below the frequency of
|
||||
// heart beat.
|
||||
if (--count_down == 0)
|
||||
{
|
||||
return check_tick_slow(p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifdef __cpp_concepts
|
||||
# include "../ds/concept.h"
|
||||
# include "pal_consts.h"
|
||||
|
||||
# include "pal_ds.h"
|
||||
# include <utility>
|
||||
|
||||
namespace snmalloc
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../ds/defines.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
@@ -85,70 +86,7 @@ namespace snmalloc
|
||||
/**
|
||||
* Default Tag ID for the Apple class
|
||||
*/
|
||||
static const uint8_t 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 = nullptr;
|
||||
|
||||
void (*pal_notify)(PalNotificationObject* self);
|
||||
|
||||
PalNotificationObject(void (*pal_notify)(PalNotificationObject* self))
|
||||
: pal_notify(pal_notify)
|
||||
{}
|
||||
};
|
||||
|
||||
/***
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
static const int PALAnonDefaultID = 241;
|
||||
|
||||
/**
|
||||
* Query whether the PAL supports a specific feature.
|
||||
|
||||
163
src/pal/pal_ds.h
Normal file
163
src/pal/pal_ds.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/defines.h"
|
||||
#include "../ds/helpers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<typename T>
|
||||
class PalList
|
||||
{
|
||||
/**
|
||||
* List of callbacks to notify
|
||||
*/
|
||||
std::atomic<T*> elements{nullptr};
|
||||
|
||||
static_assert(
|
||||
std::is_same<decltype(T::pal_next), std::atomic<T*>>::value,
|
||||
"Required pal_next type.");
|
||||
|
||||
public:
|
||||
/**
|
||||
* Add an element to the list
|
||||
*/
|
||||
void add(T* element)
|
||||
{
|
||||
auto prev = &elements;
|
||||
auto curr = prev->load();
|
||||
do
|
||||
{
|
||||
while (curr != nullptr)
|
||||
{
|
||||
prev = &(curr->pal_next);
|
||||
curr = prev->load();
|
||||
}
|
||||
} while (!prev->compare_exchange_weak(curr, element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies function to all the elements of the list
|
||||
*/
|
||||
void apply_all(function_ref<void(T*)> func)
|
||||
{
|
||||
T* curr = elements;
|
||||
while (curr != nullptr)
|
||||
{
|
||||
func(curr);
|
||||
curr = curr->pal_next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = nullptr;
|
||||
|
||||
void (*pal_notify)(PalNotificationObject* self);
|
||||
|
||||
PalNotificationObject(void (*pal_notify)(PalNotificationObject* self))
|
||||
: pal_notify(pal_notify)
|
||||
{}
|
||||
};
|
||||
|
||||
/***
|
||||
* Wrapper for managing notifications for PAL events
|
||||
*/
|
||||
class PalNotifier
|
||||
{
|
||||
/**
|
||||
* List of callbacks to notify
|
||||
*/
|
||||
PalList<PalNotificationObject> callbacks;
|
||||
|
||||
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)
|
||||
{
|
||||
callbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the pal_notify of all the registered objects.
|
||||
*/
|
||||
void notify_all()
|
||||
{
|
||||
callbacks.apply_all([](auto curr) { curr->pal_notify(curr); });
|
||||
}
|
||||
};
|
||||
|
||||
class PalTimerObject
|
||||
{
|
||||
friend class PalTimer;
|
||||
template<typename T>
|
||||
friend class PalList;
|
||||
|
||||
std::atomic<PalTimerObject*> pal_next;
|
||||
|
||||
void (*pal_notify)(PalTimerObject* self);
|
||||
|
||||
uint64_t last_run = 0;
|
||||
uint64_t repeat;
|
||||
|
||||
public:
|
||||
PalTimerObject(void (*pal_notify)(PalTimerObject* self), uint64_t repeat)
|
||||
: pal_notify(pal_notify), repeat(repeat)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple mechanism for handling timers.
|
||||
*
|
||||
* Note: This is really designed for a very small number of timers,
|
||||
* and this design should be changed if that is no longer the case.
|
||||
*/
|
||||
class PalTimer
|
||||
{
|
||||
/**
|
||||
* List of callbacks to notify
|
||||
*/
|
||||
PalList<PalTimerObject> timers;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Register a callback to be called every repeat milliseconds.
|
||||
*/
|
||||
void register_timer(PalTimerObject* timer)
|
||||
{
|
||||
timers.add(timer);
|
||||
}
|
||||
|
||||
void check(uint64_t time_ms)
|
||||
{
|
||||
static std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
|
||||
// Depulicate calls into here, and make single threaded.
|
||||
if (lock.test_and_set())
|
||||
{
|
||||
timers.apply_all([time_ms](PalTimerObject* curr) {
|
||||
if (
|
||||
(curr->last_run == 0) ||
|
||||
((time_ms - curr->last_run) > curr->repeat))
|
||||
{
|
||||
curr->last_run = time_ms;
|
||||
curr->pal_notify(curr);
|
||||
}
|
||||
});
|
||||
lock.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../aal/aal.h"
|
||||
#include "pal_concept.h"
|
||||
#include "pal_consts.h"
|
||||
#include "pal_timer_default.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -27,7 +28,7 @@ namespace snmalloc
|
||||
* ever use.
|
||||
*/
|
||||
template<SNMALLOC_CONCEPT(PALNoAllocBase) BasePAL>
|
||||
struct PALNoAlloc
|
||||
struct PALNoAlloc : public PalTimerDefaultImpl<BasePAL>
|
||||
{
|
||||
/**
|
||||
* Bitmap of PalFeatures flags indicating the optional features that this
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/bits.h"
|
||||
#include "pal_timer_default.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
// Can be extended
|
||||
// Will require a reserve method in subclasses.
|
||||
template<class State>
|
||||
class PALPlainMixin : public State
|
||||
class PALPlainMixin : public State, public PalTimerDefaultImpl<State>
|
||||
{
|
||||
public:
|
||||
// Notify platform that we will not be using these pages
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# include <iostream>
|
||||
#endif
|
||||
#include "../ds/address.h"
|
||||
#include "pal_timer_default.h"
|
||||
#if defined(SNMALLOC_BACKTRACE_HEADER)
|
||||
# include SNMALLOC_BACKTRACE_HEADER
|
||||
#endif
|
||||
@@ -39,7 +40,7 @@ namespace snmalloc
|
||||
* version.
|
||||
*/
|
||||
template<class OS>
|
||||
class PALPOSIX
|
||||
class PALPOSIX : public PalTimerDefaultImpl<PALPOSIX<OS>>
|
||||
{
|
||||
/**
|
||||
* Helper class to access the `default_mmap_flags` field of `OS` if one
|
||||
@@ -348,5 +349,19 @@ namespace snmalloc
|
||||
}
|
||||
error("Entropy requested on platform that does not provide entropy");
|
||||
}
|
||||
|
||||
static uint64_t internal_time_in_ms()
|
||||
{
|
||||
auto hold = KeepErrno();
|
||||
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
|
||||
{
|
||||
error("Failed to get time");
|
||||
}
|
||||
|
||||
return (static_cast<uint64_t>(ts.tv_sec) * 1000) +
|
||||
(static_cast<uint64_t>(ts.tv_nsec) / 1000000);
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
|
||||
32
src/pal/pal_timer_default.h
Normal file
32
src/pal/pal_timer_default.h
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pal_consts.h"
|
||||
#include "pal_ds.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
template<typename PalTime>
|
||||
class PalTimerDefaultImpl
|
||||
{
|
||||
inline static PalTimer timers{};
|
||||
|
||||
public:
|
||||
static uint64_t time_in_ms()
|
||||
{
|
||||
auto time = PalTime::internal_time_in_ms();
|
||||
|
||||
// Process timers
|
||||
timers.check(time);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
static void register_timer(PalTimerObject* timer)
|
||||
{
|
||||
timers.register_timer(timer);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../ds/address.h"
|
||||
#include "../ds/bits.h"
|
||||
#include "pal_timer_default.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifndef _MSC_VER
|
||||
@@ -22,9 +23,11 @@
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# include <chrono>
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
class PALWindows
|
||||
class PALWindows : public PalTimerDefaultImpl<PALWindows>
|
||||
{
|
||||
/**
|
||||
* A flag indicating that we have tried to register for low-memory
|
||||
@@ -206,6 +209,14 @@ namespace snmalloc
|
||||
error("Failed to get entropy.");
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint64_t internal_time_in_ms()
|
||||
{
|
||||
return static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user