[PATCH v2] rtld: cache cpuid results on the stack for intel
Sunil Pandey
skpgkp2@gmail.com
Tue Jun 9 17:54:24 GMT 2026
On Sat, Jun 6, 2026 at 11:36 AM Fabian Rast <fabian.rast@tum.de> wrote:
> dl_init_cacheinfo retrieves various information about cache
> sizes, using the cpuid instruction on x86.
> Previously, the same cpuid leaves were queried multiple times.
> This behavior caused intel_check_word to prominently show up in
> profiles of dynamic loader startup on the Intel(R) Xeon(R) Gold 6430.
> The big performance impact could not be reproduced on other Intel cpus.
>
> This patch reduces the number of cpuid queries on startup
> by caching their results on the stack for reuse when searching for a
> different cache size value.
> This approach does not change the overall design of
> the cache enumeration code (repeated calls to handle_* functions).
> The values are cached on the stack instead of globally (e.g.
> in the cpu_features global) because they are never needed after
> early initialization.
>
> The cache is only active for Intel cpus, because it has not yet
> been shown through benchmarks that it meaningfully improves performance
> for other processors.
>
> Signed-off-by: Fabian Rast <fabian.rast@tum.de>
> ---
> sysdeps/x86/dl-cacheinfo.h | 120 ++++++++++++++++++++++---------------
> 1 file changed, 73 insertions(+), 47 deletions(-)
>
> diff --git a/sysdeps/x86/dl-cacheinfo.h b/sysdeps/x86/dl-cacheinfo.h
> index 84817a84fd..53c9102bca 100644
> --- a/sysdeps/x86/dl-cacheinfo.h
> +++ b/sysdeps/x86/dl-cacheinfo.h
> @@ -108,6 +108,14 @@ static const struct intel_02_cache_info
>
> #define nintel_02_known (sizeof (intel_02_known) / sizeof (intel_02_known
> [0]))
>
> +/* Cache for redundant cpuid queries in handle_intel, intel_check_word and
> + get_common_cache_info. Currently, this has only been shown to
> significantly
> + improve performance on a specific Intel CPU (Xeon 6430). */
> +struct intel_cpuid_cache {
> + char leaf2_valid, leaf4_valid; /* Number of cached (sub)leaves. */
> + unsigned int leaf2[4], leaf4[0x10][4];
> +};
> +
> static int
> intel_02_known_compare (const void *p1, const void *p2)
> {
> @@ -128,7 +136,8 @@ static long int
> __attribute__ ((noinline))
> intel_check_word (int name, unsigned int value, bool *has_level_2,
> bool *no_level_2_or_3,
> - const struct cpu_features *cpu_features)
> + const struct cpu_features *cpu_features,
> + struct intel_cpuid_cache *cache)
> {
> if ((value & 0x80000000) != 0)
> /* The register value is reserved. */
> @@ -162,7 +171,21 @@ intel_check_word (int name, unsigned int value, bool
> *has_level_2,
> unsigned int round = 0;
> while (1)
> {
> - __cpuid_count (4, round, eax, ebx, ecx, edx);
> + if (round < cache->leaf4_valid)
> + /* Subleaf was queried before. Do not execute cpuid again.
> */
> + eax = cache->leaf4[round][0], ebx = cache->leaf4[round][1],
> + ecx = cache->leaf4[round][2], edx = cache->leaf4[round][3];
> + else if (round == cache->leaf4_valid
> + && round <
> sizeof(cache->leaf4)/sizeof(*cache->leaf4))
> + {
> + /* Cache the cpuid result if we have space. */
> + __cpuid_count (4, round, eax, ebx, ecx, edx);
> + cache->leaf4[round][0] = eax, cache->leaf4[round][1] =
> ebx;
> + cache->leaf4[round][2] = ecx, cache->leaf4[round][3] =
> edx;
> + cache->leaf4_valid++;
> + }
> + else
> + __cpuid_count (4, round, eax, ebx, ecx, edx);
>
> enum { null = 0, data = 1, inst = 2, uni = 3 } type = eax &
> 0x1f;
> if (type == null)
> @@ -258,7 +281,8 @@ intel_check_word (int name, unsigned int value, bool
> *has_level_2,
>
>
> static long int __attribute__ ((noinline))
> -handle_intel (int name, const struct cpu_features *cpu_features)
> +handle_intel (int name, const struct cpu_features *cpu_features,
> + struct intel_cpuid_cache *cache)
> {
> unsigned int maxidx = cpu_features->basic.max_cpuid;
>
> @@ -271,41 +295,33 @@ handle_intel (int name, const struct cpu_features
> *cpu_features)
> long int result = 0;
> bool no_level_2_or_3 = false;
> bool has_level_2 = false;
> - unsigned int eax;
> - unsigned int ebx;
> - unsigned int ecx;
> - unsigned int edx;
> - __cpuid (2, eax, ebx, ecx, edx);
> + int i;
> +
> + if (!cache->leaf2_valid)
> + {
> + __cpuid (2, cache->leaf2[0], cache->leaf2[1],
> + cache->leaf2[2], cache->leaf2[3]);
> + cache->leaf2_valid = 1;
> + }
>
> /* The low byte of EAX of CPUID leaf 2 should always return 1 and it
> should be ignored. If it isn't 1, use CPUID leaf 4 instead. */
> - if ((eax & 0xff) != 1)
> + if ((cache->leaf2[0] & 0xff) != 1)
> return intel_check_word (name, 0xff, &has_level_2, &no_level_2_or_3,
> - cpu_features);
> - else
> - {
> - eax &= 0xffffff00;
> + cpu_features, cache);
>
> - /* Process the individual registers' value. */
> - result = intel_check_word (name, eax, &has_level_2,
> - &no_level_2_or_3, cpu_features);
> - if (result != 0)
> - return result;
> -
> - result = intel_check_word (name, ebx, &has_level_2,
> - &no_level_2_or_3, cpu_features);
> - if (result != 0)
> - return result;
> + /* Process all descriptors in leaf 2. */
> + result = intel_check_word (name, cache->leaf2[0]&0xffffff00,
> &has_level_2,
> + &no_level_2_or_3, cpu_features, cache);
> + if (result != 0)
> + return result;
>
> - result = intel_check_word (name, ecx, &has_level_2,
> - &no_level_2_or_3, cpu_features);
> - if (result != 0)
> - return result;
> -
> - result = intel_check_word (name, edx, &has_level_2,
> - &no_level_2_or_3, cpu_features);
> + for (i = 1; i < 4; i++)
> + {
> + result = intel_check_word (name, cache->leaf2[i], &has_level_2,
> + &no_level_2_or_3, cpu_features, cache);
> if (result != 0)
> - return result;
> + return result;
> }
>
> if (name >= _SC_LEVEL2_CACHE_SIZE && name <= _SC_LEVEL3_CACHE_LINESIZE
> @@ -779,7 +795,7 @@ handle_hygon (int name)
>
> static void
> get_common_cache_info (long int *shared_ptr, long int *
> shared_per_thread_ptr, unsigned int *threads_ptr,
> - long int core)
> + long int core, struct intel_cpuid_cache *cache)
> {
> unsigned int eax;
> unsigned int ebx;
> @@ -837,7 +853,14 @@ get_common_cache_info (long int *shared_ptr, long int
> * shared_per_thread_ptr, u
> int check = 0x1 | (threads_l3 == 0) << 1;
> do
> {
> - __cpuid_count (4, i++, eax, ebx, ecx, edx);
> + if (cache && i < cache->leaf4_valid)
> + eax = cache->leaf4[i][0], ebx = cache->leaf4[i][1],
> + ecx = cache->leaf4[i][2], edx = cache->leaf4[i][3];
> + else
> + /* Do not attempt to cache queries at this point,
> + because get_common_cache_info is called last. */
> + __cpuid_count (4, i, eax, ebx, ecx, edx);
> + i++;
>
> /* There seems to be a bug in at least some Pentium Ds
> which sometimes fail to iterate all cache parameters.
> @@ -1017,35 +1040,38 @@ dl_init_cacheinfo (struct cpu_features
> *cpu_features)
>
> if (cpu_features->basic.kind == arch_kind_intel)
> {
> - data = handle_intel (_SC_LEVEL1_DCACHE_SIZE, cpu_features);
> - shared = handle_intel (_SC_LEVEL3_CACHE_SIZE, cpu_features);
> + struct intel_cpuid_cache cache;
> + cache.leaf2_valid = cache.leaf4_valid = 0;
> +
> + data = handle_intel (_SC_LEVEL1_DCACHE_SIZE, cpu_features, &cache);
> + shared = handle_intel (_SC_LEVEL3_CACHE_SIZE, cpu_features, &cache);
> shared_per_thread = shared;
>
> level1_icache_size
> - = handle_intel (_SC_LEVEL1_ICACHE_SIZE, cpu_features);
> + = handle_intel (_SC_LEVEL1_ICACHE_SIZE, cpu_features, &cache);
> level1_icache_linesize
> - = handle_intel (_SC_LEVEL1_ICACHE_LINESIZE, cpu_features);
> + = handle_intel (_SC_LEVEL1_ICACHE_LINESIZE, cpu_features, &cache);
> level1_dcache_size = data;
> level1_dcache_assoc
> - = handle_intel (_SC_LEVEL1_DCACHE_ASSOC, cpu_features);
> + = handle_intel (_SC_LEVEL1_DCACHE_ASSOC, cpu_features, &cache);
> level1_dcache_linesize
> - = handle_intel (_SC_LEVEL1_DCACHE_LINESIZE, cpu_features);
> + = handle_intel (_SC_LEVEL1_DCACHE_LINESIZE, cpu_features, &cache);
> level2_cache_size
> - = handle_intel (_SC_LEVEL2_CACHE_SIZE, cpu_features);
> + = handle_intel (_SC_LEVEL2_CACHE_SIZE, cpu_features, &cache);
> level2_cache_assoc
> - = handle_intel (_SC_LEVEL2_CACHE_ASSOC, cpu_features);
> + = handle_intel (_SC_LEVEL2_CACHE_ASSOC, cpu_features, &cache);
> level2_cache_linesize
> - = handle_intel (_SC_LEVEL2_CACHE_LINESIZE, cpu_features);
> + = handle_intel (_SC_LEVEL2_CACHE_LINESIZE, cpu_features, &cache);
> level3_cache_size = shared;
> level3_cache_assoc
> - = handle_intel (_SC_LEVEL3_CACHE_ASSOC, cpu_features);
> + = handle_intel (_SC_LEVEL3_CACHE_ASSOC, cpu_features, &cache);
> level3_cache_linesize
> - = handle_intel (_SC_LEVEL3_CACHE_LINESIZE, cpu_features);
> + = handle_intel (_SC_LEVEL3_CACHE_LINESIZE, cpu_features, &cache);
> level4_cache_size
> - = handle_intel (_SC_LEVEL4_CACHE_SIZE, cpu_features);
> + = handle_intel (_SC_LEVEL4_CACHE_SIZE, cpu_features, &cache);
>
> get_common_cache_info (&shared, &shared_per_thread, &threads,
> - level2_cache_size);
> + level2_cache_size, &cache);
> }
> else if (cpu_features->basic.kind == arch_kind_zhaoxin)
> {
> @@ -1066,7 +1092,7 @@ dl_init_cacheinfo (struct cpu_features *cpu_features)
> level3_cache_linesize = handle_zhaoxin (_SC_LEVEL3_CACHE_LINESIZE);
>
> get_common_cache_info (&shared, &shared_per_thread, &threads,
> - level2_cache_size);
> + level2_cache_size, NULL);
> }
> else if (cpu_features->basic.kind == arch_kind_amd)
> {
> --
> 2.54.0
>
>
>
> Hi!
>
> I added some comments to the patch. I also changed
> how the first descriptor of leaf2[eax] is excluded from
> bitshifting back to masking it out, to avoid messing
> up the most significant bit check in intel_check word.
>
> I have tested that this produces the same cpuid related
> output with --list-diagnostics on my i5-4300U, which
> unfortunately is the only intel cpu i have access to right now.
>
> Let me know if there is anything else I can do to help.
>
> Cheers,
> Fabian
>
LGTM
Reviewed-by: Sunil K Pandey <skpgkp2@gmail.com>
Thanks,
Sunil
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260609/92376d30/attachment-0001.htm>
More information about the Libc-alpha
mailing list