系统相关
首页 > 系统相关> > c – 当前的x86架构是否支持非临时负载(来自“正常”内存)?

c – 当前的x86架构是否支持非临时负载(来自“正常”内存)?

作者:互联网

我知道关于这个主题的多个问题,但是,我没有看到任何明确的答案或任何基准测量.因此,我创建了一个简单的程序,它使用两个整数数组.第一个阵列a非常大(64 MB),第二个阵列b很小以适合L1缓存.程序迭代a并以模块化的意义将其元素添加到b的相应元素(当到达b的末尾时,程序再次从其开始开始).对于不同大小的b,测量的L1高速缓存未命中数如下:

enter image description here

测量是在具有32 kiB L1数据高速缓存的Xeon E5 2680v3 Haswell型CPU上进行的.因此,在所有情况下,b都装入L1缓存中.然而,大约16 kiB的内存占用量大大增加了未命中数.这可能是预期的,因为a和b的加载导致此时从b的开头的高速缓存行无效.

绝对没有理由保留缓存中的元素,它们只使用一次.因此,我运行了一个带有非暂时性数据加载的程序变量,但未命中数没有改变.我还运行了一个带有非暂时预取数据的变体,但仍然具有相同的结果.

我的基准代码如下(没有显示非时间预取的变体):

int main(int argc, char* argv[])
{
   uint64_t* a;
   const uint64_t a_bytes = 64 * 1024 * 1024;
   const uint64_t a_count = a_bytes / sizeof(uint64_t);
   posix_memalign((void**)(&a), 64, a_bytes);

   uint64_t* b;
   const uint64_t b_bytes = atol(argv[1]) * 1024;
   const uint64_t b_count = b_bytes / sizeof(uint64_t);
   posix_memalign((void**)(&b), 64, b_bytes);

   __m256i ones = _mm256_set1_epi64x(1UL);
   for (long i = 0; i < a_count; i += 4)
       _mm256_stream_si256((__m256i*)(a + i), ones);

   // load b into L1 cache
   for (long i = 0; i < b_count; i++)
       b[i] = 0;

   int papi_events[1] = { PAPI_L1_DCM };
   long long papi_values[1];
   PAPI_start_counters(papi_events, 1);

   uint64_t* a_ptr = a;
   const uint64_t* a_ptr_end = a + a_count;
   uint64_t* b_ptr = b;
   const uint64_t* b_ptr_end = b + b_count;

   while (a_ptr < a_ptr_end) {
#ifndef NTLOAD
      __m256i aa = _mm256_load_si256((__m256i*)a_ptr);
#else
      __m256i aa = _mm256_stream_load_si256((__m256i*)a_ptr);
#endif
      __m256i bb = _mm256_load_si256((__m256i*)b_ptr);
      bb = _mm256_add_epi64(aa, bb);
      _mm256_store_si256((__m256i*)b_ptr, bb);

      a_ptr += 4;
      b_ptr += 4;
      if (b_ptr >= b_ptr_end)
         b_ptr = b;
   }

   PAPI_stop_counters(papi_values, 1);
   std::cout << "L1 cache misses: " << papi_values[0] << std::endl;

   free(a);
   free(b);
}

我想知道的是CPU供应商是否支持或将支持非临时加载/预取或任何其他方式如何将某些数据标记为未在缓存中保持(例如,将它们标记为LRU).例如,在HPC中存在类似情况在实践中常见的情况.例如,在稀疏迭代线性求解器/本征解算器中,矩阵数据通常非常大(大于高速缓存容量),但向量有时小到足以适应L3甚至L2高速缓存.然后,我们想不惜一切代价将它们留在那里.遗憾的是,加载矩阵数据可能导致特别是x向量高速缓存行无效,即使在每个求解器迭代中,矩阵元素仅使用一次,并且没有理由在处理之后将它们保留在高速缓存中.

UPDATE

我刚刚在Intel Xeon Phi KNC上进行了类似的实验,同时测量了运行时间而不是L1未命中(我还没有找到一种如何可靠地测量它们的方法; PAPI和VTune提供了奇怪的指标.)结果如下:

enter image description here

橙色曲线表示普通载荷,它具有预期的形状.蓝色曲线表示在指令前缀中设置了所谓的逐出提示(EH)的负载,灰色曲线表示手动驱逐a的每个高速缓存行的情况; KNC启用的这些技巧显然可以按照我们想要的16 kiB来实现.测量循环的代码如下:

while (a_ptr < a_ptr_end) {
#ifdef NTLOAD
   __m512i aa = _mm512_extload_epi64((__m512i*)a_ptr,
      _MM_UPCONV_EPI64_NONE, _MM_BROADCAST64_NONE, _MM_HINT_NT);
#else
   __m512i aa = _mm512_load_epi64((__m512i*)a_ptr);
#endif
   __m512i bb = _mm512_load_epi64((__m512i*)b_ptr);
   bb = _mm512_or_epi64(aa, bb);
   _mm512_store_epi64((__m512i*)b_ptr, bb);

#ifdef EVICT
   _mm_clevict(a_ptr, _MM_HINT_T0);
#endif

   a_ptr += 8;
   b_ptr += 8;
   if (b_ptr >= b_ptr_end)
       b_ptr = b;
}

更新2

在Xeon Phi上,icpc为a_ptr的正常加载变量(橙色曲线)预取生成:

400e93:       62 d1 78 08 18 4c 24    vprefetch0 [r12+0x80]

当我手动(通过十六进制编辑可执行文件)修改为:

400e93:       62 d1 78 08 18 44 24    vprefetchnta [r12+0x80]

我得到了所需的结果,甚至比蓝/灰曲线更好.但是,我无法强制编译器为我生成非临时prefetchnig,即使在循环之前使用#pragma prefetch a_ptr:_MM_HINT_NTA

标签:c-3,prefetch,c,x86,caching
来源: https://codeday.me/bug/20190926/1821017.html