gpt4 book ai didi

c - 使用时间戳计数器测量内存延迟

转载 作者:行者123 更新时间:2023-12-01 12:10:21 26 4
gpt4 key购买 nike

我编写了以下代码,它首先刷新两个数组元素,然后尝试读取元素以测量命中/未命中延迟。

#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>
#include <time.h>
int main()
{
/* create array */
int array[ 100 ];
int i;
for ( i = 0; i < 100; i++ )
array[ i ] = i; // bring array to the cache

uint64_t t1, t2, ov, diff1, diff2, diff3;

/* flush the first cache line */
_mm_lfence();
_mm_clflush( &array[ 30 ] );
_mm_clflush( &array[ 70 ] );
_mm_lfence();

/* READ MISS 1 */
_mm_lfence(); // fence to keep load order
t1 = __rdtsc(); // set start time
_mm_lfence();
int tmp = array[ 30 ]; // read the first elemet => cache miss
_mm_lfence();
t2 = __rdtsc(); // set stop time
_mm_lfence();

diff1 = t2 - t1; // two fence statements are overhead
printf( "tmp is %d\ndiff1 is %lu\n", tmp, diff1 );

/* READ MISS 2 */
_mm_lfence(); // fence to keep load order
t1 = __rdtsc(); // set start time
_mm_lfence();
tmp = array[ 70 ]; // read the second elemet => cache miss (or hit due to prefetching?!)
_mm_lfence();
t2 = __rdtsc(); // set stop time
_mm_lfence();

diff2 = t2 - t1; // two fence statements are overhead
printf( "tmp is %d\ndiff2 is %lu\n", tmp, diff2 );


/* READ HIT*/
_mm_lfence(); // fence to keep load order
t1 = __rdtsc(); // set start time
_mm_lfence();
tmp = array[ 30 ]; // read the first elemet => cache hit
_mm_lfence();
t2 = __rdtsc(); // set stop time
_mm_lfence();

diff3 = t2 - t1; // two fence statements are overhead
printf( "tmp is %d\ndiff3 is %lu\n", tmp, diff3 );


/* measuring fence overhead */
_mm_lfence();
t1 = __rdtsc();
_mm_lfence();
_mm_lfence();
t2 = __rdtsc();
_mm_lfence();
ov = t2 - t1;

printf( "lfence overhead is %lu\n", ov );
printf( "cache miss1 TSC is %lu\n", diff1-ov );
printf( "cache miss2 (or hit due to prefetching) TSC is %lu\n", diff2-ov );
printf( "cache hit TSC is %lu\n", diff3-ov );


return 0;
}

输出是
# gcc -O3 -o simple_flush simple_flush.c
# taskset -c 0 ./simple_flush
tmp is 30
diff1 is 529
tmp is 70
diff2 is 222
tmp is 30
diff3 is 46
lfence overhead is 32
cache miss1 TSC is 497
cache miss2 (or hit due to prefetching) TSC is 190
cache hit TSC is 14
# taskset -c 0 ./simple_flush
tmp is 30
diff1 is 486
tmp is 70
diff2 is 276
tmp is 30
diff3 is 46
lfence overhead is 32
cache miss1 TSC is 454
cache miss2 (or hit due to prefetching) TSC is 244
cache hit TSC is 14
# taskset -c 0 ./simple_flush
tmp is 30
diff1 is 848
tmp is 70
diff2 is 222
tmp is 30
diff3 is 46
lfence overhead is 34
cache miss1 TSC is 814
cache miss2 (or hit due to prefetching) TSC is 188
cache hit TSC is 12

读取输出 array[70]有一些问题. TSC 既不会被击中也不会失手。我已经刷新了类似于 array[30] 的那个项目.一种可能是当 array[40]被访问,硬件预取器带来 array[70] .所以,这应该是一个打击。然而,TSC 不仅仅是一个打击。当我尝试阅读 array[30] 时,您可以验证命中的 TSC 大约为 20。第二次。

甚至,如果 array[70]未预取,TSC 应该类似于缓存未命中。

有什么理由吗?

更新1:

为了读取数组,我尝试了 (void) *((int*)array+i)正如彼得和哈迪所建议的那样。

在输出中,我看到许多负面结果。我的意思是开销似乎大于 (void) *((int*)array+i)
更新2:

我忘了加 volatile .现在的结果是有意义的。

最佳答案

首先,请注意对 printf 的两次调用测量后diff1diff2可能会扰乱 L1D 甚至 L2 的状态。在我的系统上,使用 printfdiff3-ov 的报告值范围在 4-48 个周期之间(我已经配置了我的系统,使 TSC 频率大约等于核心频率)。最常见的值是 L2 和 L3 延迟的值。如果报告的值为 8,那么我们的 L1D 缓存命中了。如果它大于 8,那么很可能是前面对 printf 的调用已经从 L1D 和可能的 L2(在一些罕见的情况下,是 L3!)中踢出了目标缓存线,这可以解释测量到的延迟高于 8。@PeterCordes 有 suggested使用 (void) *((volatile int*)array + i)而不是 temp = array[i]; printf(temp) .进行此更改后,我的实验表明,大多数报告的测量值 diff3-ov正好是 8 个周期(这表明测量误差约为 4 个周期),并且报告的唯一其他值是 0、4 和 12。因此,强烈建议使用 Peter 的方法。

一般来说,主内存访问延迟取决于许多因素,包括 MMU 缓存的状态和页表遍历器对数据缓存的影响、核心频率、非核心频率、内存 Controller 的状态和配置以及与目标物理地址、非核心争用和超线程导致的核心争用有关的内存芯片。 array[70]可能位于与 array[30] 不同的虚拟页面(和物理页面)中它们的加载指令 IP 和目标内存位置的地址可能以复杂的方式与预取器交互。所以cache miss1的原因可能有很多。不同于 cache miss2 .进行彻底的调查是可能的,但正如您想象的那样,这需要付出很多努力。通常,如果您的核心频率大于 1.5 GHz(比高性能 Intel 处理器上的 TSC frequency 小),那么 L3 加载未命中至少需要 60 个核心周期。在您的情况下,两个未命中延迟都超过 100 个周期,因此这些很可能是 L3 未命中。但在一些极其罕见的情况下,cache miss2似乎接近 L3 或 L2 延迟范围,这可能是由于预取造成的。

我已经确定以下代码在 Haswell 上给出了统计上更准确的测量:

t1 = __rdtscp(&dummy);
tmp = *((volatile int*)array + 30);
asm volatile ("add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
: "+r" (tmp));
t2 = __rdtscp(&dummy);
t2 = __rdtscp(&dummy);
loadlatency = t2 - t1 - 60; // 60 is the overhead
loadlatency的概率是 4 个周期是 97%。 loadlatency的概率是 8 个周期是 1.7%。 loadlatency的概率采用其他值是 1.3%。所有其他值都大于 8 且是 4 的倍数。稍后我将尝试添加解释。

关于c - 使用时间戳计数器测量内存延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52083481/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com