#ifndef JEMALLOC_INTERNAL_DECAY_H #define JEMALLOC_INTERNAL_DECAY_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 */