| #include "jemalloc/internal/jemalloc_preamble.h" |
| #include "jemalloc/internal/jemalloc_internal_includes.h" |
| |
| bool |
| edata_cache_init(edata_cache_t *edata_cache, base_t *base) { |
| edata_avail_new(&edata_cache->avail); |
| /* |
| * This is not strictly necessary, since the edata_cache_t is only |
| * created inside an arena, which is zeroed on creation. But this is |
| * handy as a safety measure. |
| */ |
| atomic_store_zu(&edata_cache->count, 0, ATOMIC_RELAXED); |
| if (malloc_mutex_init(&edata_cache->mtx, "edata_cache", |
| WITNESS_RANK_EDATA_CACHE, malloc_mutex_rank_exclusive)) { |
| return true; |
| } |
| edata_cache->base = base; |
| return false; |
| } |
| |
| edata_t * |
| edata_cache_get(tsdn_t *tsdn, edata_cache_t *edata_cache) { |
| malloc_mutex_lock(tsdn, &edata_cache->mtx); |
| edata_t *edata = edata_avail_first(&edata_cache->avail); |
| if (edata == NULL) { |
| malloc_mutex_unlock(tsdn, &edata_cache->mtx); |
| return base_alloc_edata(tsdn, edata_cache->base); |
| } |
| edata_avail_remove(&edata_cache->avail, edata); |
| atomic_load_sub_store_zu(&edata_cache->count, 1); |
| malloc_mutex_unlock(tsdn, &edata_cache->mtx); |
| return edata; |
| } |
| |
| void |
| edata_cache_put(tsdn_t *tsdn, edata_cache_t *edata_cache, edata_t *edata) { |
| malloc_mutex_lock(tsdn, &edata_cache->mtx); |
| edata_avail_insert(&edata_cache->avail, edata); |
| atomic_load_add_store_zu(&edata_cache->count, 1); |
| malloc_mutex_unlock(tsdn, &edata_cache->mtx); |
| } |
| |
| void |
| edata_cache_prefork(tsdn_t *tsdn, edata_cache_t *edata_cache) { |
| malloc_mutex_prefork(tsdn, &edata_cache->mtx); |
| } |
| |
| void |
| edata_cache_postfork_parent(tsdn_t *tsdn, edata_cache_t *edata_cache) { |
| malloc_mutex_postfork_parent(tsdn, &edata_cache->mtx); |
| } |
| |
| void |
| edata_cache_postfork_child(tsdn_t *tsdn, edata_cache_t *edata_cache) { |
| malloc_mutex_postfork_child(tsdn, &edata_cache->mtx); |
| } |
| |
| void |
| edata_cache_fast_init(edata_cache_fast_t *ecs, edata_cache_t *fallback) { |
| edata_list_inactive_init(&ecs->list); |
| ecs->fallback = fallback; |
| ecs->disabled = false; |
| } |
| |
| static void |
| edata_cache_fast_try_fill_from_fallback(tsdn_t *tsdn, |
| edata_cache_fast_t *ecs) { |
| edata_t *edata; |
| malloc_mutex_lock(tsdn, &ecs->fallback->mtx); |
| for (int i = 0; i < EDATA_CACHE_FAST_FILL; i++) { |
| edata = edata_avail_remove_first(&ecs->fallback->avail); |
| if (edata == NULL) { |
| break; |
| } |
| edata_list_inactive_append(&ecs->list, edata); |
| atomic_load_sub_store_zu(&ecs->fallback->count, 1); |
| } |
| malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); |
| } |
| |
| edata_t * |
| edata_cache_fast_get(tsdn_t *tsdn, edata_cache_fast_t *ecs) { |
| witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), |
| WITNESS_RANK_EDATA_CACHE, 0); |
| |
| if (ecs->disabled) { |
| assert(edata_list_inactive_first(&ecs->list) == NULL); |
| return edata_cache_get(tsdn, ecs->fallback); |
| } |
| |
| edata_t *edata = edata_list_inactive_first(&ecs->list); |
| if (edata != NULL) { |
| edata_list_inactive_remove(&ecs->list, edata); |
| return edata; |
| } |
| /* Slow path; requires synchronization. */ |
| edata_cache_fast_try_fill_from_fallback(tsdn, ecs); |
| edata = edata_list_inactive_first(&ecs->list); |
| if (edata != NULL) { |
| edata_list_inactive_remove(&ecs->list, edata); |
| } else { |
| /* |
| * Slowest path (fallback was also empty); allocate something |
| * new. |
| */ |
| edata = base_alloc_edata(tsdn, ecs->fallback->base); |
| } |
| return edata; |
| } |
| |
| static void |
| edata_cache_fast_flush_all(tsdn_t *tsdn, edata_cache_fast_t *ecs) { |
| /* |
| * You could imagine smarter cache management policies (like |
| * only flushing down to some threshold in anticipation of |
| * future get requests). But just flushing everything provides |
| * a good opportunity to defrag too, and lets us share code between the |
| * flush and disable pathways. |
| */ |
| edata_t *edata; |
| size_t nflushed = 0; |
| malloc_mutex_lock(tsdn, &ecs->fallback->mtx); |
| while ((edata = edata_list_inactive_first(&ecs->list)) != NULL) { |
| edata_list_inactive_remove(&ecs->list, edata); |
| edata_avail_insert(&ecs->fallback->avail, edata); |
| nflushed++; |
| } |
| atomic_load_add_store_zu(&ecs->fallback->count, nflushed); |
| malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); |
| } |
| |
| void |
| edata_cache_fast_put(tsdn_t *tsdn, edata_cache_fast_t *ecs, edata_t *edata) { |
| witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), |
| WITNESS_RANK_EDATA_CACHE, 0); |
| |
| if (ecs->disabled) { |
| assert(edata_list_inactive_first(&ecs->list) == NULL); |
| edata_cache_put(tsdn, ecs->fallback, edata); |
| return; |
| } |
| |
| /* |
| * Prepend rather than append, to do LIFO ordering in the hopes of some |
| * cache locality. |
| */ |
| edata_list_inactive_prepend(&ecs->list, edata); |
| } |
| |
| void |
| edata_cache_fast_disable(tsdn_t *tsdn, edata_cache_fast_t *ecs) { |
| edata_cache_fast_flush_all(tsdn, ecs); |
| ecs->disabled = true; |
| } |