Xeon CPU (E5-2603) 向后内存预取

2024-02-04

Xeon CPU (E5-2603) 中的向后内存预取与向前内存预取一样快吗?

我想实现一种需要对数据进行前向循环和后向循环的算法。

由于每次迭代都需要上次迭代的结果,因此我无法反转循环的顺序。


您可以运行实验来确定数据预取器是否能够处理前向顺序访问和后向顺序访问。我有一个 Haswell CPU,因此预取器可能与您的 CPU (Sandy Bridge) 中实现的预取器不同。

下图显示了以四种不同方式遍历数组时每个元素访问可观察到的延迟:

  • 数组向前顺序初始化,然后以同样的方式遍历。我将这种模式称为forfor.
  • 数组先向前顺序初始化,然后向后顺序遍历(从最后一个元素到第一个元素)。我将这种模式称为forback.
  • 数组按向后顺序初始化,然后以同样的方式遍历。我将这种模式称为backback.

x 轴表示元素索引,y 轴表示 TSC 周期中的延迟。我已配置我的系统,使 TSC 周期大约等于核心周期。我已经绘制了两次运行的测量值forfor called forfor1 and forfor2。每个元素的平均延迟如下:

  • forfor1:9.9 个周期。
  • forfor2:15 个周期。
  • forback:35.8 个周期。
  • backback:40.3 个周期。

L1 访问延迟对任何测量噪声都特别敏感。 L2 访问延迟应该是12个周期 https://www.7-cpu.com/cpu/Haswell.html平均而言,但由于周期数较少的噪声,我们可能仍会在 L1 命中时得到 12 个周期的延迟。在第一轮运行中forfor,大多数延迟是 4 个周期,这清楚地表明 L1 命中。在第二轮比赛中forfor,大多数延迟为 8 或 12 个周期。我认为这些也可能是 L1 热门歌曲。在这两种情况下,都有一些 L3 命中和很少的主存访问。对彼此而言forback and backback,我们可以看到大多数延迟都是 L3 命中。这意味着 L3 预取器能够处理向前和向后遍历,但 L1 和 L2 预取器则不能。

然而,访问是一个接一个地快速连续执行的,其间基本上没有计算。因此,如果 L2 预取器确实尝试向后预取,它可能会太晚获取数据,因此仍然会产生类似 L3 的延迟。

请注意,我没有在数组的两次遍历之间刷新缓存,因此第一次遍历可能会影响第二次遍历中测量的延迟。

这是我用来进行测量的代码。

/* compile with gcc at optimization level -O3 */
/* set the minimum and maximum CPU frequency for all cores using cpupower to get meaningful results */ 
/* run using "sudo nice -n -20 ./a.out" to minimize possible context switches, or at least use "taskset -c 0 ./a.out" */
/* make sure all cache prefetchers are enabled */
/* preferrably disable HT */
/* this code is Intel-specific */
/* see the note at the end of the answer */

#include <stdint.h>
#include <x86intrin.h>
#include <stdio.h>

// 2048 iterations.
#define LINES_SIZE 64
#define ITERATIONS 2048 * LINES_SIZE
// Forward
#define START 0
#define END ITERATIONS
// Backward
//#define START ITERATIONS - LINES_SIZE
//#define END 0
#if START < END
#define INCREMENT i = i + LINES_SIZE
#define COMP <
#else
#define INCREMENT i = i - LINES_SIZE
#define COMP >=
#endif

int main()
{
  int array[ ITERATIONS ];
  int latency[ ITERATIONS/LINES_SIZE ];
  uint64_t time1, time2, al, osl; /* initial values don't matter */

  // Perhaps necessary to prevents UB?
  for ( int i = 0; i < ITERATIONS; i = i + LINES_SIZE )
  {
     array[ i ] = i; 
  }

  printf( "address = %p \n", &array[ 0 ] ); /* guaranteed to be aligned within a single cache line */

  // Measure overhead.
  _mm_mfence();                      
  _mm_lfence();                      /* mfence and lfence must be in this order + compiler barrier for rdtsc */
  time1 = __rdtsc();                 /* set timer */
  _mm_lfence();                      /* serialize rdtsc with respect to trailing instructions + compiler barrier for rdtsc */
  /* no need for mfence because there are no stores in between */
  _mm_lfence();                      /* mfence and lfence must be in this order + compiler barrier for rdtsc */
  time2 = __rdtsc();
  _mm_lfence();                      /* serialize rdtsc with respect to trailing instructions */
  osl = time2 - time1;

  // Forward or backward traversal.
  for ( int i = START; i COMP END; INCREMENT )
  {

     _mm_mfence();                      /* this properly orders both clflush and rdtsc */
     _mm_lfence();                      /* mfence and lfence must be in this order + compiler barrier for rdtsc */
     time1 = __rdtsc();                 /* set timer */
     _mm_lfence();                      /* serialize rdtsc with respect to trailing instructions + compiler barrier for rdtsc */
     int temp = array[ i ];             /* access array[i] */
     _mm_lfence();                      /* mfence and lfence must be in this order + compiler barrier for rdtsc */
     time2 = __rdtsc();
     _mm_lfence();                      /* serialize rdtsc with respect to trailing instructions */
     al = time2 - time1;

     printf( "array[ %i ] = %i \n", i, temp );         /* prevent the compiler from optimizing the load */
     latency[i/64] = al - osl;

  }

  // Output measured latencies.
  for ( int i = 0; i < ITERATIONS/LINES_SIZE; ++i )
  {
     printf( "%i \n", latency[i] );
  }

  return 0;
}

这些实验的目的是测量各个访问延迟,以确定每次访问从哪个缓存级别提供服务。然而,由于存在LFENCE指令时,测量结果可能包括加载指令在流水线的其他阶段所需的延迟。此外,编译器将一些 ALU 指令放置在定时区域中,因此测量可能会受到这些指令的影响(可以通过在汇编中编写代码来避免这种情况)。这可能会导致难以区分 L1 中命中的访问和 L2 中命中的访问。例如,一些 L1 延迟测量报告为 8 个周期。尽管如此,forback and backback测量结果清楚地表明大多数访问都在 L3 中命中。

如果我们有兴趣测量访问内存层次结构的特定级别的平均延迟,那么使用指针追踪可以提供更准确的结果。事实上,这是测量内存延迟的传统方法。

如果您以硬件预取器(尤其是 L2 或 L3 的预取器)难以预测的模式访问大量数据,则软件预取器可能非常有用。然而,一般来说,正确地进行软件预取是很困难的。此外,我得到的测量结果表明 L3 预取器可以向前和向后预取。如果在内存访问和计算方面都具有良好的并行性,那么 OoO 执行可以隐藏很大一部分 L3 访问延迟。


关于正确运行程序的重要注意事项:事实证明,如果我没有使用输出重定向运算符 > 将所有输出重定向到文件,即所有输出都将打印在终端上,则所有测量的延迟将接近 L3 命中延迟。这样做的原因是printf每次迭代都会调用它,它会污染大部分 L1 和 L2 缓存。因此请务必使用 > 运算符。您还可以使用(void) *((volatile int*)array + i)代替int tmp = array[i]如提议的this https://stackoverflow.com/a/51977139/4230618 and this https://stackoverflow.com/a/52086874/4230618回答。这样就更靠谱了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Xeon CPU (E5-2603) 向后内存预取 的相关文章

  • 为什么x86分页没有特权环的概念?

    早在 1982 年 当 Intel 发布 80286 时 他们在分段方案中添加了 4 个特权级别 环 0 3 由全局描述符表 GDT 和局部描述符表 LDT 中的 2 位指定 在 80386 处理器中 Intel 添加了分页功能 但令人惊讶
  • 在未排序的整数列表中最优搜索 k 个最小值

    我刚刚接受采访时提出了一个问题 我很好奇答案应该是什么 问题本质上是 假设您有一个包含 n 个整数的未排序列表 您如何找到此列表中的 k 个最小值 也就是说 如果您有一个 10 11 24 12 13 列表并且正在寻找 2 个最小值 您将得
  • MSMQ 慢速队列读取

    我正在使用一个开源 Net 库 它在底层使用 MSMQ 大约一两周后 服务速度变慢 时间不准确 但一般猜测 看来发生的情况是来自 MSMQ 的消息每 10 秒才被读取一次 通常 它们会立即被读取 因此 它们将在 T 10 秒 T 20 秒
  • 为什么比较匹配的字符串比比较不匹配的字符串更快? [复制]

    这个问题在这里已经有答案了 这里有两个测量值 timeit timeit toto 1234 number 100000000 1 8320042459999968 timeit timeit toto toto number 100000
  • 内容长度标头与分块编码

    我正在尝试权衡设置的利弊Content LengthHTTP 标头与使用分块编码从我的服务器返回 可能 大文件的比较 使用持久连接需要其中之一来符合 HTTP 1 1 规范 我看到了的优点Content Length标头是 下载对话框可以显
  • 在 R 中替换数据帧中最低列表值的最有效方法

    我有一个数据框 df 其中包含为每个受试者记录的数字列表 向量 用于测试项目的两次重复 subj item rep vec s1 1 1 2 1 4 5 8 4 7 s1 1 2 1 1 3 4 7 5 3 s1 2 1 6 5 4 1 2
  • Flask:缓存静态文件(.js、.css)

    我真的找不到任何这方面的资源 那么如何将视图 函数的缓存与静态文件 即 css js 分开 我想将静态对象缓存一周 另一方面 我只需要缓存函数 视图几分钟 当我执行以下操作时 from flask ext cache import Cach
  • Nasm 打印到下一行

    我用 nasm Assembly 编写了以下程序 section text global start start Input variables mov edx inLen mov ecx inMsg mov ebx 1 mov eax 4
  • 为什么在强度降低乘法和循环进位加法之后,这段代码的执行速度会变慢?

    我正在读书阿格纳 雾 https en wikipedia org wiki Agner Fog s 优化手册 https en wikipedia org wiki Agner Fog Optimization 我遇到了这个例子 doub
  • 为什么 hibernate 在 SAVE 之前执行 SELECT?

    为什么 hibernate 在保存对象之前要进行选择 我在互联网上找不到有用的信息 这是每次保存之前的正常行为吗 我发现这个话题 选择 hibernateTemplate save 的查询运行 https stackoverflow com
  • glBlitFramebuffer 渲染缓冲区和渲染全屏纹理哪个更快?

    哪个更快更高效 使用 OpenGL 纹理作为 CUDA 表面并在四边形上渲染 新样式 使用渲染缓冲区作为 CUDA 表面并使用 glBlitFramebuffer 进行渲染 None
  • Angularjs 在生产中禁用调试数据

    我正在尝试按照角度文档中的建议禁用生产服务器中的调试数据here https docs angularjs org guide production 补充一点 我并没有真正看到性能和加载时间有任何改进 这是我的代码在 app js 中的样子
  • 如何减少 JSF 中的 javax.faces.ViewState

    减少 JSF 中视图状态隐藏字段大小的最佳方法是什么 我注意到我的视图状态约为 40k 这会在每次请求和响应时下降到客户端并返回到服务器 特别是到达服务器时 这对用户来说会显着减慢 我的环境 JSF 1 2 MyFaces Tomcat T
  • 在 SPA 中加载外部脚本和样式文件

    我有一种 SPA 它使用 API 来获取数据 该 SPA 有一些实例 它们都使用通用样式和脚本文件 所以我的问题是 当我更改这些文件中的一行时 我将必须打开每个实例并更新文件 这对我来说真的很耗时 一种方法是将这些文件放在服务器中的文件夹中
  • 更改二维数组元素的值会更改整个列

    当我打印我的arrvalue 我得到了 2D 数组的正确值 但是当我退出 while 循环时 我的值都是错误的 我不确定我做错了什么 num runs n 4 x np linspace 1 1 n y np linspace 1 1 n
  • LINQ 函数的顺序重要吗?

    基本上 正如问题所述 LINQ 函数的顺序是否重要 表现 显然 结果仍然必须相同 Example myCollection OrderBy item gt item CreatedDate Where item gt item Code g
  • Java 基准测试 - 为什么第二个循环更快?

    我对此很好奇 我想检查哪个函数更快 所以我创建了一些代码并执行了很多次 public static void main String args long ts String c sgfrt34tdfg34 ts System current
  • 为什么在排序输入上插入到树中比随机输入更快?

    现在我一直听说从随机选择的数据构建二叉搜索树比有序数据更快 这仅仅是因为有序数据需要显式重新平衡以将树高度保持在最低限度 最近我实现了一个不可变的treap http en wikipedia org wiki Treap 一种特殊的二叉搜
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手
  • 为什么 std::atomic 比 volatile bool 慢很多?

    多年来我一直使用 volatile bool 来控制线程执行 并且效果很好 in my class declaration volatile bool stop In the thread function while stop do th

随机推荐