【问题标题】:cpu cacheline and prefetch policycpu 缓存线和预取策略
【发布时间】:2013-12-13 14:42:18
【问题描述】:

我读了这篇文章http://igoro.com/archive/gallery-of-processor-cache-effects/。文章说因为cacheline延迟,代码:

int[] arr = new int[64 * 1024 * 1024];

// Loop 1
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

// Loop 2
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;

几乎有相同的执行时间,我写了一些示例 c 代码来测试它。我在带有 Ubuntu 64 位的 Xeon(R) E3-1230 V2、带有 Debian 的 ARMv6 兼容处理器 rev 7 上运行代码,并且还在 Core 2 T6600 上运行它。所有结果都不是文章所说的。

我的代码如下:

long int jobTime(struct timespec start, struct timespec stop) {
    long int seconds = stop.tv_sec - start.tv_sec;
    long int nsec = stop.tv_nsec - start.tv_nsec;
    return seconds * 1000 * 1000 * 1000 + nsec;
}

int main() {
    struct timespec start;
    struct timespec stop;
    int i = 0;
    struct sched_param param;
    int * arr = malloc(LENGTH * 4);

    printf("---------sieofint %d\n", sizeof(int));
    param.sched_priority = 0;
    sched_setscheduler(0, SCHED_FIFO, &param);
    //clock_gettime(CLOCK_MONOTONIC, &start);
    //for (i = 0; i < LENGTH; i++) arr[i] *= 5;
    //clock_gettime(CLOCK_MONOTONIC, &stop);

    //printf("step %d : time %ld\n", 1, jobTime(start, stop));

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (i = 0; i < LENGTH; i += 2) arr[i] *= 5;
    clock_gettime(CLOCK_MONOTONIC, &stop);

    printf("step %d : time %ld\n", 2, jobTime(start, stop));
}

每次我选择一个片段来编译和运行(注释一个并取消注释另一个)。 编译:

gcc -O0 -o cache cache.c -lrt

在 Xeon 上我明白了:

step 1 : 258791478
step 2 : 97875746

我想知道文章所说的是否正确?或者,最新的 cpu 是否有更高级的预取策略?

【问题讨论】:

  • 您的LENGTH 设置为什么?
  • #define LENGTH (64 * 1024 * 1024)
  • 您不考虑进程交换、系统调用、缓存预热逻辑、涡轮增压。换句话说,您的测量结果不正确。
  • 弗拉德我使用 FIFO 进行处理,并将所有 printf 移到底部,我还将 scaling_governor 核心设置为“性能”,我仍然得到相同的结果。您能否提供一个代码示例来执行此操作。谢谢。
  • @Vlad Lazarenko 你能举个例子吗?

标签: c cpu-architecture cpu-cache


【解决方案1】:

简短回答 (TL;DR):您正在访问未初始化的数据,您的第一个循环必须在定时循环内为整个数组分配新的物理页。


当我运行您的代码并依次注释每个部分时,两个循环的时间几乎相同。但是,当我取消注释这两个部分并一个接一个地运行它们时,我确实得到了与您报告的结果相同的结果。这让我怀疑你也这样做了,并且在比较第一个循环和第二个循环时受到 冷启动 影响。很容易检查 - 只需更换循环的顺序,看看第一个是否仍然更慢。

为了避免,要么选择一个足够大的LENGTH(取决于你的系统),这样你就不会从第一个循环帮助第二个循环中获得任何缓存好处,或者只是添加一个没有计时的整个数组的单次遍历。

请注意,第二个选项并不能完全证明博客想要表达的意思 - 内存延迟掩盖了执行延迟,因此无论您使用多少缓存行的元素,您仍然会遇到瓶颈内存访问时间(或更准确地说 - 带宽)

另外 - 使用 -O0 对代码进行基准测试是一种非常糟糕的做法


编辑:

这就是我得到的(删除了日程安排,因为它不相关)。
这段代码:

for (i = 0; i < LENGTH; i++) arr[i] = 1;   // warmup!

clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; i < LENGTH; i++) arr[i] *= 5;
clock_gettime(CLOCK_MONOTONIC, &stop);
printf("step %d : time %ld\n", 1, jobTime(start, stop));

clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; i < LENGTH; i+=16) arr[i] *= 5;
clock_gettime(CLOCK_MONOTONIC, &stop);

给:

---------sieofint 4
step 1 : time 58862552
step 16 : time 50215446

虽然评论热身线与您在第二个循环中报告的优势相同:

---------sieofint 4
step 1 : time 279772411
step 16 : time 50615420

替换循环的顺序(预热仍然有注释)表明它确实与步长无关,而是与顺序有关:

---------sieofint 4
step 16 : time 250033980
step 1 : time 59168310

(gcc 版本 4.6.3,在 Opteron 6272 上)

现在请注意这里发生的事情 - 理论上,只有当数组小到可以放在某个缓存中时,您才会期望预热才有意义 - 在这种情况下,您使用的 LENGTH 即使对于大多数机器上的 L3。但是,您忘记了页面地图 - 您不只是跳过数据本身的预热 - 您首先避免了初始化它。这永远无法在现实生活中为您提供有意义的结果,但由于这是一个您没有注意到的基准,您只是将垃圾数据乘以它的延迟。

这意味着您在第一个循环中访问的每个新页面不仅会进入内存,它可能会出现页面错误,并且必须调用操作系统为其映射新的物理页面 >。这是一个漫长的过程,乘以您使用的 4K 页面的数量 - 累积到很长时间。在这种阵列大小下,您甚至无法从 TLB 中受益(您有 16k 个不同的物理 4k 页面,这比大多数 TLB 甚至可以支持 2 个级别还要多),所以这只是故障流的问题。这可能是任何分析工具都可以测量的。

在同一个数组上的第二次迭代不会产生这种效果并且会更快 - 即使仍然必须在每个新页面上执行完整的页面遍历(这完全在硬件中完成),然后从记忆。

顺便说一句,这也是当您对某些行为进行基准测试时,您多次重复相同的事情的原因(在这种情况下,如果您以相同的步幅多次运行数组,它会解决您的问题,并且忽略了前几​​轮)。

【讨论】:

  • 抱歉,我发布的代码是“for (i = 0; i
  • 和 lenth 是“#define LENGTH (64 * 1024 * 1024)”
  • 我注意到了这一点,并在回答之前进行了修复。在循环解决问题之前添加for (i = 0; i &lt; LENGTH; i++) arr[i] = 1; 仍然相同,请参阅我的编辑。
  • 有线,交换订单没有预热,结果是:第16步:时间97183406第1步:时间171714412和预热:第16步:时间46210167第1步:时间169496367我想可能是太大了对于 LENGTH 以防止 TLB 丢失,所以我将 LENGTH 更改为“64 * 1024”,结果是:步骤 16:时间 26941 步骤 1:时间 379591 并将 LENGTH 更改为“1024”,结果是:步骤 16:时间 584 第 1 步:时间 6196
  • @user3099672 - 尺寸越小,导致此映射降级的页面就越少,但您也会受到缓存影响,您可能会开始遭受驱逐。您是否更改为-O3 并多次测量每个循环(平均结果)?
猜你喜欢
  • 2012-09-28
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
  • 2012-09-05
  • 2020-08-03
  • 2011-11-12
  • 2011-01-18
  • 1970-01-01
相关资源
最近更新 更多