mirror of
https://github.com/jemalloc/jemalloc
synced 2026-06-08 15:01:07 +00:00
c067a55c79
Converting size to usize is what jemalloc has been done by ceiling size to the closest size class. However, this causes lots of memory wastes with HPA enabled. This commit changes how usize is calculated so that the gap between two contiguous usize is no larger than a page. Specifically, this commit includes the following changes: 1. Adding a build-time config option (--enable-limit-usize-gap) and a runtime one (limit_usize_gap) to guard the changes. When build-time config is enabled, some minor CPU overhead is expected because usize will be stored and accessed apart from index. When runtime option is also enabled (it can only be enabled with the build-time config enabled). a new usize calculation approach wil be employed. This new calculation will ceil size to the closest multiple of PAGE for all sizes larger than USIZE_GROW_SLOW_THRESHOLD instead of using the size classes. Note when the build-time config is enabled, the runtime option is default on. 2. Prepare tcache for size to grow by PAGE over GROUP*PAGE. To prepare for the upcoming changes where size class grows by PAGE when larger than NGROUP * PAGE, disable the tcache when it is larger than 2 * NGROUP * PAGE. The threshold for tcache is set higher to prevent perf regression as much as possible while usizes between NGROUP * PAGE and 2 * NGROUP * PAGE happen to grow by PAGE. 3. Prepare pac and hpa psset for size to grow by PAGE over GROUP*PAGE For PAC, to avoid having too many bins, arena bins still have the same layout. This means some extra search is needed for a page-level request that is not aligned with the orginal size class: it should also search the heap before the current index since the previous heap might also be able to have some allocations satisfying it. The same changes apply to HPA's psset. This search relies on the enumeration of the heap because not all allocs in the previous heap are guaranteed to satisfy the request. To balance the memory and CPU overhead, we currently enumerate at most a fixed number of nodes before concluding none can satisfy the request during an enumeration. 4. Add bytes counter to arena large stats. To prepare for the upcoming usize changes, stats collected by multiplying alive allocations and the bin size is no longer accurate. Thus, add separate counters to record the bytes malloced and dalloced. 5. Change structs use when freeing to avoid using index2size for large sizes. - Change the definition of emap_alloc_ctx_t - Change the read of both from edata_t. - Change the assignment and usage of emap_alloc_ctx_t. - Change other callsites of index2size. Note for the changes in the data structure, i.e., emap_alloc_ctx_t, will be used when the build-time config (--enable-limit-usize-gap) is enabled but they will store the same value as index2size(szind) if the runtime option (opt_limit_usize_gap) is not enabled. 6. Adapt hpa to the usize changes. Change the settings in sec to limit is usage for sizes larger than USIZE_GROW_SLOW_THRESHOLD and modify corresponding tests. 7. Modify usize calculation and corresponding tests. Change the sz_s2u_compute. Note sz_index2size is not always safe now while sz_size2index still works as expected.
423 lines
12 KiB
C
423 lines
12 KiB
C
#ifndef JEMALLOC_INTERNAL_HPDATA_H
|
|
#define JEMALLOC_INTERNAL_HPDATA_H
|
|
|
|
#include "jemalloc/internal/jemalloc_preamble.h"
|
|
#include "jemalloc/internal/fb.h"
|
|
#include "jemalloc/internal/nstime.h"
|
|
#include "jemalloc/internal/pages.h"
|
|
#include "jemalloc/internal/ph.h"
|
|
#include "jemalloc/internal/ql.h"
|
|
#include "jemalloc/internal/typed_list.h"
|
|
|
|
/*
|
|
* The metadata representation we use for extents in hugepages. While the PAC
|
|
* uses the edata_t to represent both active and inactive extents, the HP only
|
|
* uses the edata_t for active ones; instead, inactive extent state is tracked
|
|
* within hpdata associated with the enclosing hugepage-sized, hugepage-aligned
|
|
* region of virtual address space.
|
|
*
|
|
* An hpdata need not be "truly" backed by a hugepage (which is not necessarily
|
|
* an observable property of any given region of address space). It's just
|
|
* hugepage-sized and hugepage-aligned; it's *potentially* huge.
|
|
*/
|
|
|
|
/*
|
|
* The max enumeration num should not exceed 2^16 - 1, see comments in edata.h
|
|
* for ESET_ENUMERATE_MAX_NUM for more details.
|
|
*/
|
|
#define PSSET_ENUMERATE_MAX_NUM 32
|
|
typedef struct hpdata_s hpdata_t;
|
|
ph_structs(hpdata_age_heap, hpdata_t, PSSET_ENUMERATE_MAX_NUM);
|
|
struct hpdata_s {
|
|
/*
|
|
* We likewise follow the edata convention of mangling names and forcing
|
|
* the use of accessors -- this lets us add some consistency checks on
|
|
* access.
|
|
*/
|
|
|
|
/*
|
|
* The address of the hugepage in question. This can't be named h_addr,
|
|
* since that conflicts with a macro defined in Windows headers.
|
|
*/
|
|
void *h_address;
|
|
/* Its age (measured in psset operations). */
|
|
uint64_t h_age;
|
|
/* Whether or not we think the hugepage is mapped that way by the OS. */
|
|
bool h_huge;
|
|
|
|
/*
|
|
* For some properties, we keep parallel sets of bools; h_foo_allowed
|
|
* and h_in_psset_foo_container. This is a decoupling mechanism to
|
|
* avoid bothering the hpa (which manages policies) from the psset
|
|
* (which is the mechanism used to enforce those policies). This allows
|
|
* all the container management logic to live in one place, without the
|
|
* HPA needing to know or care how that happens.
|
|
*/
|
|
|
|
/*
|
|
* Whether or not the hpdata is allowed to be used to serve allocations,
|
|
* and whether or not the psset is currently tracking it as such.
|
|
*/
|
|
bool h_alloc_allowed;
|
|
bool h_in_psset_alloc_container;
|
|
|
|
/*
|
|
* The same, but with purging. There's no corresponding
|
|
* h_in_psset_purge_container, because the psset (currently) always
|
|
* removes hpdatas from their containers during updates (to implement
|
|
* LRU for purging).
|
|
*/
|
|
bool h_purge_allowed;
|
|
|
|
/* And with hugifying. */
|
|
bool h_hugify_allowed;
|
|
/* When we became a hugification candidate. */
|
|
nstime_t h_time_hugify_allowed;
|
|
bool h_in_psset_hugify_container;
|
|
|
|
/* Whether or not a purge or hugify is currently happening. */
|
|
bool h_mid_purge;
|
|
bool h_mid_hugify;
|
|
|
|
/*
|
|
* Whether or not the hpdata is being updated in the psset (i.e. if
|
|
* there has been a psset_update_begin call issued without a matching
|
|
* psset_update_end call). Eventually this will expand to other types
|
|
* of updates.
|
|
*/
|
|
bool h_updating;
|
|
|
|
/* Whether or not the hpdata is in a psset. */
|
|
bool h_in_psset;
|
|
|
|
union {
|
|
/* When nonempty (and also nonfull), used by the psset bins. */
|
|
hpdata_age_heap_link_t age_link;
|
|
/*
|
|
* When empty (or not corresponding to any hugepage), list
|
|
* linkage.
|
|
*/
|
|
ql_elm(hpdata_t) ql_link_empty;
|
|
};
|
|
|
|
/*
|
|
* Linkage for the psset to track candidates for purging and hugifying.
|
|
*/
|
|
ql_elm(hpdata_t) ql_link_purge;
|
|
ql_elm(hpdata_t) ql_link_hugify;
|
|
|
|
/* The length of the largest contiguous sequence of inactive pages. */
|
|
size_t h_longest_free_range;
|
|
|
|
/* Number of active pages. */
|
|
size_t h_nactive;
|
|
|
|
/* A bitmap with bits set in the active pages. */
|
|
fb_group_t active_pages[FB_NGROUPS(HUGEPAGE_PAGES)];
|
|
|
|
/*
|
|
* Number of dirty or active pages, and a bitmap tracking them. One
|
|
* way to think of this is as which pages are dirty from the OS's
|
|
* perspective.
|
|
*/
|
|
size_t h_ntouched;
|
|
|
|
/* The touched pages (using the same definition as above). */
|
|
fb_group_t touched_pages[FB_NGROUPS(HUGEPAGE_PAGES)];
|
|
};
|
|
|
|
TYPED_LIST(hpdata_empty_list, hpdata_t, ql_link_empty)
|
|
TYPED_LIST(hpdata_purge_list, hpdata_t, ql_link_purge)
|
|
TYPED_LIST(hpdata_hugify_list, hpdata_t, ql_link_hugify)
|
|
|
|
ph_proto(, hpdata_age_heap, hpdata_t);
|
|
|
|
static inline void *
|
|
hpdata_addr_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_address;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_addr_set(hpdata_t *hpdata, void *addr) {
|
|
assert(HUGEPAGE_ADDR2BASE(addr) == addr);
|
|
hpdata->h_address = addr;
|
|
}
|
|
|
|
static inline uint64_t
|
|
hpdata_age_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_age;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_age_set(hpdata_t *hpdata, uint64_t age) {
|
|
hpdata->h_age = age;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_huge_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_huge;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_alloc_allowed_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_alloc_allowed;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_alloc_allowed_set(hpdata_t *hpdata, bool alloc_allowed) {
|
|
hpdata->h_alloc_allowed = alloc_allowed;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_in_psset_alloc_container_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_in_psset_alloc_container;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_in_psset_alloc_container_set(hpdata_t *hpdata, bool in_container) {
|
|
assert(in_container != hpdata->h_in_psset_alloc_container);
|
|
hpdata->h_in_psset_alloc_container = in_container;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_purge_allowed_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_purge_allowed;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_purge_allowed_set(hpdata_t *hpdata, bool purge_allowed) {
|
|
assert(purge_allowed == false || !hpdata->h_mid_purge);
|
|
hpdata->h_purge_allowed = purge_allowed;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_hugify_allowed_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_hugify_allowed;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_allow_hugify(hpdata_t *hpdata, nstime_t now) {
|
|
assert(!hpdata->h_mid_hugify);
|
|
hpdata->h_hugify_allowed = true;
|
|
hpdata->h_time_hugify_allowed = now;
|
|
}
|
|
|
|
static inline nstime_t
|
|
hpdata_time_hugify_allowed(hpdata_t *hpdata) {
|
|
return hpdata->h_time_hugify_allowed;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_disallow_hugify(hpdata_t *hpdata) {
|
|
hpdata->h_hugify_allowed = false;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_in_psset_hugify_container_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_in_psset_hugify_container;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_in_psset_hugify_container_set(hpdata_t *hpdata, bool in_container) {
|
|
assert(in_container != hpdata->h_in_psset_hugify_container);
|
|
hpdata->h_in_psset_hugify_container = in_container;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_mid_purge_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_mid_purge;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_mid_purge_set(hpdata_t *hpdata, bool mid_purge) {
|
|
assert(mid_purge != hpdata->h_mid_purge);
|
|
hpdata->h_mid_purge = mid_purge;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_mid_hugify_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_mid_hugify;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_mid_hugify_set(hpdata_t *hpdata, bool mid_hugify) {
|
|
assert(mid_hugify != hpdata->h_mid_hugify);
|
|
hpdata->h_mid_hugify = mid_hugify;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_changing_state_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_mid_purge || hpdata->h_mid_hugify;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
hpdata_updating_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_updating;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_updating_set(hpdata_t *hpdata, bool updating) {
|
|
assert(updating != hpdata->h_updating);
|
|
hpdata->h_updating = updating;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_in_psset_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_in_psset;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_in_psset_set(hpdata_t *hpdata, bool in_psset) {
|
|
assert(in_psset != hpdata->h_in_psset);
|
|
hpdata->h_in_psset = in_psset;
|
|
}
|
|
|
|
static inline size_t
|
|
hpdata_longest_free_range_get(const hpdata_t *hpdata) {
|
|
return hpdata->h_longest_free_range;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_longest_free_range_set(hpdata_t *hpdata, size_t longest_free_range) {
|
|
assert(longest_free_range <= HUGEPAGE_PAGES);
|
|
hpdata->h_longest_free_range = longest_free_range;
|
|
}
|
|
|
|
static inline size_t
|
|
hpdata_nactive_get(hpdata_t *hpdata) {
|
|
return hpdata->h_nactive;
|
|
}
|
|
|
|
static inline size_t
|
|
hpdata_ntouched_get(hpdata_t *hpdata) {
|
|
return hpdata->h_ntouched;
|
|
}
|
|
|
|
static inline size_t
|
|
hpdata_ndirty_get(hpdata_t *hpdata) {
|
|
return hpdata->h_ntouched - hpdata->h_nactive;
|
|
}
|
|
|
|
static inline size_t
|
|
hpdata_nretained_get(hpdata_t *hpdata) {
|
|
return HUGEPAGE_PAGES - hpdata->h_ntouched;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_assert_empty(hpdata_t *hpdata) {
|
|
assert(fb_empty(hpdata->active_pages, HUGEPAGE_PAGES));
|
|
assert(hpdata->h_nactive == 0);
|
|
}
|
|
|
|
/*
|
|
* Only used in tests, and in hpdata_assert_consistent, below. Verifies some
|
|
* consistency properties of the hpdata (e.g. that cached counts of page stats
|
|
* match computed ones).
|
|
*/
|
|
static inline bool
|
|
hpdata_consistent(hpdata_t *hpdata) {
|
|
if(fb_urange_longest(hpdata->active_pages, HUGEPAGE_PAGES)
|
|
!= hpdata_longest_free_range_get(hpdata)) {
|
|
return false;
|
|
}
|
|
if (fb_scount(hpdata->active_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)
|
|
!= hpdata->h_nactive) {
|
|
return false;
|
|
}
|
|
if (fb_scount(hpdata->touched_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)
|
|
!= hpdata->h_ntouched) {
|
|
return false;
|
|
}
|
|
if (hpdata->h_ntouched < hpdata->h_nactive) {
|
|
return false;
|
|
}
|
|
if (hpdata->h_huge && hpdata->h_ntouched != HUGEPAGE_PAGES) {
|
|
return false;
|
|
}
|
|
if (hpdata_changing_state_get(hpdata)
|
|
&& ((hpdata->h_purge_allowed) || hpdata->h_hugify_allowed)) {
|
|
return false;
|
|
}
|
|
if (hpdata_hugify_allowed_get(hpdata)
|
|
!= hpdata_in_psset_hugify_container_get(hpdata)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline void
|
|
hpdata_assert_consistent(hpdata_t *hpdata) {
|
|
assert(hpdata_consistent(hpdata));
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_empty(const hpdata_t *hpdata) {
|
|
return hpdata->h_nactive == 0;
|
|
}
|
|
|
|
static inline bool
|
|
hpdata_full(const hpdata_t *hpdata) {
|
|
return hpdata->h_nactive == HUGEPAGE_PAGES;
|
|
}
|
|
|
|
void hpdata_init(hpdata_t *hpdata, void *addr, uint64_t age);
|
|
|
|
/*
|
|
* Given an hpdata which can serve an allocation request, pick and reserve an
|
|
* offset within that allocation.
|
|
*/
|
|
void *hpdata_reserve_alloc(hpdata_t *hpdata, size_t sz);
|
|
void hpdata_unreserve(hpdata_t *hpdata, void *addr, size_t sz);
|
|
|
|
/*
|
|
* The hpdata_purge_prepare_t allows grabbing the metadata required to purge
|
|
* subranges of a hugepage while holding a lock, drop the lock during the actual
|
|
* purging of them, and reacquire it to update the metadata again.
|
|
*/
|
|
typedef struct hpdata_purge_state_s hpdata_purge_state_t;
|
|
struct hpdata_purge_state_s {
|
|
size_t npurged;
|
|
size_t ndirty_to_purge;
|
|
fb_group_t to_purge[FB_NGROUPS(HUGEPAGE_PAGES)];
|
|
size_t next_purge_search_begin;
|
|
};
|
|
|
|
/*
|
|
* Initializes purge state. The access to hpdata must be externally
|
|
* synchronized with other hpdata_* calls.
|
|
*
|
|
* You can tell whether or not a thread is purging or hugifying a given hpdata
|
|
* via hpdata_changing_state_get(hpdata). Racing hugification or purging
|
|
* operations aren't allowed.
|
|
*
|
|
* Once you begin purging, you have to follow through and call hpdata_purge_next
|
|
* until you're done, and then end. Allocating out of an hpdata undergoing
|
|
* purging is not allowed.
|
|
*
|
|
* Returns the number of dirty pages that will be purged.
|
|
*/
|
|
size_t hpdata_purge_begin(hpdata_t *hpdata, hpdata_purge_state_t *purge_state);
|
|
|
|
/*
|
|
* If there are more extents to purge, sets *r_purge_addr and *r_purge_size to
|
|
* true, and returns true. Otherwise, returns false to indicate that we're
|
|
* done.
|
|
*
|
|
* This requires exclusive access to the purge state, but *not* to the hpdata.
|
|
* In particular, unreserve calls are allowed while purging (i.e. you can dalloc
|
|
* into one part of the hpdata while purging a different part).
|
|
*/
|
|
bool hpdata_purge_next(hpdata_t *hpdata, hpdata_purge_state_t *purge_state,
|
|
void **r_purge_addr, size_t *r_purge_size);
|
|
/*
|
|
* Updates the hpdata metadata after all purging is done. Needs external
|
|
* synchronization.
|
|
*/
|
|
void hpdata_purge_end(hpdata_t *hpdata, hpdata_purge_state_t *purge_state);
|
|
|
|
void hpdata_hugify(hpdata_t *hpdata);
|
|
void hpdata_dehugify(hpdata_t *hpdata);
|
|
|
|
#endif /* JEMALLOC_INTERNAL_HPDATA_H */
|