Allow M_ARENA_MAX / MALLOC_ARENA_MAX limit even with PER_THREAD disabled With the new PER_THREAD compile-time option, the allocator also offers a way to limit the total number of arenas using MALLOC_ARENA_MAX environment variable or mallopt(M_ARENA_MAX). In principle, this feature is not tied to the PER_THREAD option. This patch makes it possible to use it even with the default compilation settings. One motivation to limit the number of arenas may be libhugetlbfs users that rely on its __morecore hook providing hugetlbfs-backed memory for the allocator - this can work only with a single arena and multi-threaded programs wishing to use this feature need a way to limit the allocator to a single arena. Another motivation is avoiding pathological behavior in extremely thread-intensive applications. 2011-02-04 Petr Baudis * malloc/arena.c: Define and manage narenas even ifndef PER_THREAD. * malloc/arena.c (ptmalloc_init_minimal): Likewise. * malloc/arena.c (_int_new_arena): Likewise. * malloc/arena.c (ptmalloc_init): Implement MALLOC_ARENA_MAX even ifndef PER_THREAD. * malloc/arena.c (reused_arena): Split off get_narenas_limit(), define even ifndef PER_THREAD. * malloc/arena.c (arena_get2): Adjust for get_narenas_limit() split, call reused_arena even ifndef PER_THREAD. * malloc/hooks.c (public_gET_STATe): Set arena_max, narenas even ifndef PER_THREAD. * malloc/hooks.c (public_sET_STATe): Likewise. * malloc/malloc.c (malloc_par): Define arena_max even ifndef PER_THREAD. * malloc/malloc.c (mALLOPt): Implement M_ARENA_MAX even ifndef PER_THREAD. * malloc/malloc.c: Remove redundant M_* defines. Index: glibc-2.13/malloc/arena.c =================================================================== --- glibc-2.13.orig/malloc/arena.c +++ glibc-2.13/malloc/arena.c @@ -78,8 +78,8 @@ extern int sanity_check_heap_info_alignm static tsd_key_t arena_key; static mutex_t list_lock; -#ifdef PER_THREAD static size_t narenas; +#ifdef PER_THREAD static mstate free_list; #endif @@ -416,8 +416,8 @@ ptmalloc_init_minimal (void) #ifdef PER_THREAD # define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8)) mp_.arena_test = NARENAS_FROM_NCORES (1); - narenas = 1; #endif + narenas = 1; } @@ -574,10 +574,8 @@ ptmalloc_init (void) { if (memcmp (envline, "MMAP_MAX_", 9) == 0) mALLOPt(M_MMAP_MAX, atoi(&envline[10])); -#ifdef PER_THREAD else if (memcmp (envline, "ARENA_MAX", 9) == 0) mALLOPt(M_ARENA_MAX, atoi(&envline[10])); -#endif } break; #ifdef PER_THREAD @@ -946,9 +944,9 @@ _int_new_arena(size_t size) atomic_write_barrier (); main_arena.next = a; -#ifdef PER_THREAD ++narenas; +#ifdef PER_THREAD (void)mutex_unlock(&list_lock); #endif @@ -982,13 +980,10 @@ get_free_list (void) return result; } - -static mstate -reused_arena (void) +static int get_narenas_limit (void) __attribute__((pure)); +static int +get_narenas_limit (void) { - if (narenas <= mp_.arena_test) - return NULL; - static int narenas_limit; if (narenas_limit == 0) { @@ -1006,10 +1001,16 @@ reused_arena (void) narenas_limit = NARENAS_FROM_NCORES (2); } } + return narenas_limit; +} +#endif - if (narenas < narenas_limit) - return NULL; +/* Reuse and return one of the existing arenas; if all arenas are busy, + * pick one in a round-robin fashion and block until it becomes available. */ +static mstate +reused_arena (void) +{ mstate result; static mstate next_to_use; if (next_to_use == NULL) @@ -1035,7 +1036,6 @@ reused_arena (void) return result; } -#endif static mstate internal_function @@ -1048,10 +1048,15 @@ arena_get2(a_tsd, size) mstate a_tsd; si mstate a; #ifdef PER_THREAD - if ((a = get_free_list ()) == NULL - && (a = reused_arena ()) == NULL) - /* Nothing immediately available, so generate a new arena. */ - a = _int_new_arena(size); + if ((a = get_free_list ()) == NULL) + { + if (narenas > mp_.arena_test && narenas >= get_narenas_limit()) + a = reused_arena (); + else + /* Nothing immediately available, but we can still generate more + * arenas, so get a new one. */ + a = _int_new_arena(size); + } #else if(!a_tsd) a = a_tsd = &main_arena; @@ -1093,8 +1098,14 @@ arena_get2(a_tsd, size) mstate a_tsd; si goto repeat; } - /* Nothing immediately available, so generate a new arena. */ - a = _int_new_arena(size); + if (__builtin_expect(mp_.arena_max > 0, 0) && narenas >= mp_.arena_max) + /* Try again, this time blocking in case we are still unable to find + * a free arena. */ + a = reused_arena(); + else + /* Nothing immediately available, so generate a new arena. */ + a = _int_new_arena(size); + (void)mutex_unlock(&list_lock); #endif Index: glibc-2.13/malloc/hooks.c =================================================================== --- glibc-2.13.orig/malloc/hooks.c +++ glibc-2.13/malloc/hooks.c @@ -579,9 +579,9 @@ public_gET_STATe(void) ms->max_fast = get_max_fast(); #ifdef PER_THREAD ms->arena_test = mp_.arena_test; +#endif ms->arena_max = mp_.arena_max; ms->narenas = narenas; -#endif (void)mutex_unlock(&main_arena.mutex); return (Void_t*)ms; } @@ -683,9 +683,9 @@ public_sET_STATe(Void_t* msptr) if (ms->version >= 4) { #ifdef PER_THREAD mp_.arena_test = ms->arena_test; +#endif mp_.arena_max = ms->arena_max; narenas = ms->narenas; -#endif } check_malloc_state(&main_arena); Index: glibc-2.13/malloc/malloc.c =================================================================== --- glibc-2.13.orig/malloc/malloc.c +++ glibc-2.13/malloc/malloc.c @@ -2405,9 +2405,10 @@ struct malloc_par { INTERNAL_SIZE_T top_pad; INTERNAL_SIZE_T mmap_threshold; #ifdef PER_THREAD + /* Lower bound for arena_max. */ INTERNAL_SIZE_T arena_test; - INTERNAL_SIZE_T arena_max; #endif + INTERNAL_SIZE_T arena_max; /* Memory map support */ int n_mmaps; @@ -2445,13 +2446,6 @@ static struct malloc_state main_arena; static struct malloc_par mp_; -#ifdef PER_THREAD -/* Non public mallopt parameters. */ -#define M_ARENA_TEST -7 -#define M_ARENA_MAX -8 -#endif - - /* Maximum size of memory handled in fastbins. */ static INTERNAL_SIZE_T global_max_fast; @@ -6111,12 +6105,12 @@ int mALLOPt(param_number, value) int par if (value > 0) mp_.arena_test = value; break; +#endif case M_ARENA_MAX: if (value > 0) mp_.arena_max = value; break; -#endif } (void)mutex_unlock(&av->mutex); return res;