41e0b857be
Header files are now self-contained, which makes the relationships between the files clearer, and crucially allows LSP tools like `clangd` to function correctly in all of our header files. I have verified that the headers are self-contained (aside from the various Windows shims) by compiling them as if they were C files – in a follow-up commit I plan to add this to CI to ensure we don't regress on this front.
189 lines
6.2 KiB
C
189 lines
6.2 KiB
C
#ifndef JEMALLOC_INTERNAL_DECAY_H
|
|
#define JEMALLOC_INTERNAL_DECAY_H
|
|
|
|
#include "jemalloc/internal/jemalloc_preamble.h"
|
|
#include "jemalloc/internal/mutex.h"
|
|
#include "jemalloc/internal/smoothstep.h"
|
|
|
|
#define DECAY_UNBOUNDED_TIME_TO_PURGE ((uint64_t)-1)
|
|
|
|
/*
|
|
* The decay_t computes the number of pages we should purge at any given time.
|
|
* Page allocators inform a decay object when pages enter a decay-able state
|
|
* (i.e. dirty or muzzy), and query it to determine how many pages should be
|
|
* purged at any given time.
|
|
*
|
|
* This is mostly a single-threaded data structure and doesn't care about
|
|
* synchronization at all; it's the caller's responsibility to manage their
|
|
* synchronization on their own. There are two exceptions:
|
|
* 1) It's OK to racily call decay_ms_read (i.e. just the simplest state query).
|
|
* 2) The mtx and purging fields live (and are initialized) here, but are
|
|
* logically owned by the page allocator. This is just a convenience (since
|
|
* those fields would be duplicated for both the dirty and muzzy states
|
|
* otherwise).
|
|
*/
|
|
typedef struct decay_s decay_t;
|
|
struct decay_s {
|
|
/* Synchronizes all non-atomic fields. */
|
|
malloc_mutex_t mtx;
|
|
/*
|
|
* True if a thread is currently purging the extents associated with
|
|
* this decay structure.
|
|
*/
|
|
bool purging;
|
|
/*
|
|
* Approximate time in milliseconds from the creation of a set of unused
|
|
* dirty pages until an equivalent set of unused dirty pages is purged
|
|
* and/or reused.
|
|
*/
|
|
atomic_zd_t time_ms;
|
|
/* time / SMOOTHSTEP_NSTEPS. */
|
|
nstime_t interval;
|
|
/*
|
|
* Time at which the current decay interval logically started. We do
|
|
* not actually advance to a new epoch until sometime after it starts
|
|
* because of scheduling and computation delays, and it is even possible
|
|
* to completely skip epochs. In all cases, during epoch advancement we
|
|
* merge all relevant activity into the most recently recorded epoch.
|
|
*/
|
|
nstime_t epoch;
|
|
/* Deadline randomness generator. */
|
|
uint64_t jitter_state;
|
|
/*
|
|
* Deadline for current epoch. This is the sum of interval and per
|
|
* epoch jitter which is a uniform random variable in [0..interval).
|
|
* Epochs always advance by precise multiples of interval, but we
|
|
* randomize the deadline to reduce the likelihood of arenas purging in
|
|
* lockstep.
|
|
*/
|
|
nstime_t deadline;
|
|
/*
|
|
* The number of pages we cap ourselves at in the current epoch, per
|
|
* decay policies. Updated on an epoch change. After an epoch change,
|
|
* the caller should take steps to try to purge down to this amount.
|
|
*/
|
|
size_t npages_limit;
|
|
/*
|
|
* Number of unpurged pages at beginning of current epoch. During epoch
|
|
* advancement we use the delta between arena->decay_*.nunpurged and
|
|
* ecache_npages_get(&arena->ecache_*) to determine how many dirty pages,
|
|
* if any, were generated.
|
|
*/
|
|
size_t nunpurged;
|
|
/*
|
|
* Trailing log of how many unused dirty pages were generated during
|
|
* each of the past SMOOTHSTEP_NSTEPS decay epochs, where the last
|
|
* element is the most recent epoch. Corresponding epoch times are
|
|
* relative to epoch.
|
|
*
|
|
* Updated only on epoch advance, triggered by
|
|
* decay_maybe_advance_epoch, below.
|
|
*/
|
|
size_t backlog[SMOOTHSTEP_NSTEPS];
|
|
|
|
/* Peak number of pages in associated extents. Used for debug only. */
|
|
uint64_t ceil_npages;
|
|
};
|
|
|
|
/*
|
|
* The current decay time setting. This is the only public access to a decay_t
|
|
* that's allowed without holding mtx.
|
|
*/
|
|
static inline ssize_t
|
|
decay_ms_read(const decay_t *decay) {
|
|
return atomic_load_zd(&decay->time_ms, ATOMIC_RELAXED);
|
|
}
|
|
|
|
/*
|
|
* See the comment on the struct field -- the limit on pages we should allow in
|
|
* this decay state this epoch.
|
|
*/
|
|
static inline size_t
|
|
decay_npages_limit_get(const decay_t *decay) {
|
|
return decay->npages_limit;
|
|
}
|
|
|
|
/* How many unused dirty pages were generated during the last epoch. */
|
|
static inline size_t
|
|
decay_epoch_npages_delta(const decay_t *decay) {
|
|
return decay->backlog[SMOOTHSTEP_NSTEPS - 1];
|
|
}
|
|
|
|
/*
|
|
* Current epoch duration, in nanoseconds. Given that new epochs are started
|
|
* somewhat haphazardly, this is not necessarily exactly the time between any
|
|
* two calls to decay_maybe_advance_epoch; see the comments on fields in the
|
|
* decay_t.
|
|
*/
|
|
static inline uint64_t
|
|
decay_epoch_duration_ns(const decay_t *decay) {
|
|
return nstime_ns(&decay->interval);
|
|
}
|
|
|
|
static inline bool
|
|
decay_immediately(const decay_t *decay) {
|
|
ssize_t decay_ms = decay_ms_read(decay);
|
|
return decay_ms == 0;
|
|
}
|
|
|
|
static inline bool
|
|
decay_disabled(const decay_t *decay) {
|
|
ssize_t decay_ms = decay_ms_read(decay);
|
|
return decay_ms < 0;
|
|
}
|
|
|
|
/* Returns true if decay is enabled and done gradually. */
|
|
static inline bool
|
|
decay_gradually(const decay_t *decay) {
|
|
ssize_t decay_ms = decay_ms_read(decay);
|
|
return decay_ms > 0;
|
|
}
|
|
|
|
/*
|
|
* Returns true if the passed in decay time setting is valid.
|
|
* < -1 : invalid
|
|
* -1 : never decay
|
|
* 0 : decay immediately
|
|
* > 0 : some positive decay time, up to a maximum allowed value of
|
|
* NSTIME_SEC_MAX * 1000, which corresponds to decaying somewhere in the early
|
|
* 27th century. By that time, we expect to have implemented alternate purging
|
|
* strategies.
|
|
*/
|
|
bool decay_ms_valid(ssize_t decay_ms);
|
|
|
|
/*
|
|
* As a precondition, the decay_t must be zeroed out (as if with memset).
|
|
*
|
|
* Returns true on error.
|
|
*/
|
|
bool decay_init(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms);
|
|
|
|
/*
|
|
* Given an already-initialized decay_t, reinitialize it with the given decay
|
|
* time. The decay_t must have previously been initialized (and should not then
|
|
* be zeroed).
|
|
*/
|
|
void decay_reinit(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms);
|
|
|
|
/*
|
|
* Compute how many of 'npages_new' pages we would need to purge in 'time'.
|
|
*/
|
|
uint64_t decay_npages_purge_in(decay_t *decay, nstime_t *time,
|
|
size_t npages_new);
|
|
|
|
/* Returns true if the epoch advanced and there are pages to purge. */
|
|
bool decay_maybe_advance_epoch(decay_t *decay, nstime_t *new_time,
|
|
size_t current_npages);
|
|
|
|
/*
|
|
* Calculates wait time until a number of pages in the interval
|
|
* [0.5 * npages_threshold .. 1.5 * npages_threshold] should be purged.
|
|
*
|
|
* Returns number of nanoseconds or DECAY_UNBOUNDED_TIME_TO_PURGE in case of
|
|
* indefinite wait.
|
|
*/
|
|
uint64_t decay_ns_until_purge(decay_t *decay, size_t npages_current,
|
|
uint64_t npages_threshold);
|
|
|
|
#endif /* JEMALLOC_INTERNAL_DECAY_H */
|