[PATCH v7 1/2] malloc: add tcache support for large chunk caching
Wilco Dijkstra
Wilco.Dijkstra@arm.com
Tue May 27 19:17:13 GMT 2025
Hi Cupertino,
Thanks for the new version - this looks much more promising, including performance!
A few comments:
> # define TCACHE_MAX_BINS 64
> +# define TCACHE_LARGE_BINS 12 /* Up to 4M chunks */
> +# define TCACHE_ALL_BINS (TCACHE_MAX_BINS + TCACHE_LARGE_BINS)
> # define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)
> +# define MAX_TCACHE_LARGE_SIZE \
> + (1 << (31 - __builtin_clz (MAX_TCACHE_SIZE) + TCACHE_LARGE_BINS))
Note there is an issue with tabs here which causes merge conflicts.
It seems the macros are now confusingly named - how about:
TCACHE_MAX_BINS = TCACHE_SMALL_BINS + TCACHE_LARGE_BINS?
Plus TCACHE_SMALL_SIZE and TCACHE_LARGE_SIZE equivalents in bytes?
> - uint16_t counts[TCACHE_MAX_BINS];
> - tcache_entry *entries[TCACHE_MAX_BINS];
> + uint16_t counts[TCACHE_MAX_BINS + TCACHE_LARGE_BINS];
> + tcache_entry *entries[TCACHE_MAX_BINS + TCACHE_LARGE_BINS];
> } tcache_perthread_struct;
Keeping TCACHE_MAX_BINS as the real maximum avoids having to update this
and in a few more places.
> +static __always_inline size_t
> +large_csize2tidx(size_t nb)
> +{
> + size_t idx = TCACHE_MAX_BINS
> + + __builtin_clz (tidx2usize (TCACHE_MAX_BINS-1))
> + - __builtin_clz (nb);
> + return idx;
Could use TCACHE_SMALL_SIZE rather than tidx2usize (TCACHE_MAX_BINS-1).
> + tcache_entry **entry;
> + bool mangled = false;
> + entry = tcache_location_large (chunksize(chunk), tc_idx, &mangled);
Nit: add space before '('.
> +#define NO_TCACHE_BIN ((size_t) -1)
> +static __always_inline size_t
> +tcache_index (size_t bytes)
> +{
> + size_t tc_idx = csize2tidx (bytes);
> +
> + if (__glibc_unlikely (tcache == NULL))
> + return NO_TCACHE_BIN;
I don't believe it makes sense to introduce NO_TCACHE_BIN and explicitly
check for it. We could just use TCACHE_MAX_BINS (new definition) rather
than adding a new define.
> + if (__glibc_likely (tc_idx < TCACHE_MAX_BINS
> + && tc_idx < mp_.tcache_bins))
> + return tc_idx;
> else
> - return false;
> + {
> + tc_idx = large_csize2tidx (bytes);
> + if (tc_idx < mp_.tcache_bins)
> + return tc_idx;
Hmms, so if tc_idx >= mp_.tcache_bins but mp_.tcache_bins < TCACHE_MAX_BINS,
won't we evaluate large_csize2tidx (bytes) for a size that should use the small bins?
This feels risky and it's not clear it will work correctly...
I think we can remove the && tc_idx < mp_.tcache_bins in the first if-statement
if we keep the existing equivalent check in free() below - this avoids the issue.
+static __always_inline void *
+tcache_get_align (size_t nb, size_t alignment)
+{
+ size_t tc_idx = tcache_index (nb);
+
+ if (tc_idx != NO_TCACHE_BIN && tc_idx < TCACHE_MAX_BINS + TCACHE_LARGE_BINS
This is equivalent to just checking tc_idx < TCACHE_MAX_BINS (new definition). Same for
a few more uses of NO_TCACHE_BIN.
+ && tcache->counts[tc_idx] > 0)
> @@ -3430,7 +3543,7 @@ __libc_free (void *mem)
> #if USE_TCACHE
> size_t tc_idx = csize2tidx (size);
>
> - if (__glibc_likely (tcache != NULL && tc_idx < mp_.tcache_bins))
> + if (tcache != NULL)
Why remove the tc_idx < mp_.tcache_bins here? This means we'll insert into small
tcache even if tc_idx >= mp_.tcache_bins...
> + void *victim = tcache_get_align (checked_request2size (bytes), alignment);
> + if (victim != NULL)
> + return tag_new_usable (victim);
> + MAYBE_INIT_TCACHE ();
This bit (and related removal of existing code) also has a merge conflict due to tabs.
if (SINGLE_THREAD_P)
@@ -5482,13 +5588,22 @@ do_set_arena_max (size_t value)
static __always_inline int
do_set_tcache_max (size_t value)
{
- if (value <= MAX_TCACHE_SIZE)
+ size_t csize = request2size (value);
+ LIBC_PROBE (memory_tunable_tcache_max_bytes, 2, value, mp_.tcache_max_bytes);
+
+ if (value < MAX_TCACHE_SIZE)
Why change it from <= to < ?
{
- LIBC_PROBE (memory_tunable_tcache_max_bytes, 2, value, mp_.tcache_max_bytes);
mp_.tcache_max_bytes = value;
- mp_.tcache_bins = csize2tidx (request2size(value)) + 1;
+ mp_.tcache_bins = csize2tidx (csize) + 1;
return 1;
}
+ else if (value < MAX_TCACHE_LARGE_SIZE)
+ {
+ mp_.tcache_max_bytes = value;
+ mp_.tcache_bins = large_csize2tidx (csize) + 1;
The cutoff point is odd, if value = 2048, it caches malloc sizes up to 4072... This also
means if you use MAX_TCACHE_LARGE_SIZE-1 (another strange cutoff), it actually overflows
the tcache arrays.
Note this allows mp_.tcache_bins >= MAX_TCACHE_SIZE (current definition), but that
would break any existing uses of small tcache code that doesn't support large tcache
(such as in __libc_calloc).
So if we keep using mp_.tcache_bins instead of mp_.tcache_max_bytes, I think the only
safe way is to check the index rather than the size.
Cheers,
Wilco
More information about the Libc-alpha
mailing list