Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typo3/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 哈斯韦尔存储器存取_Performance_X86_Cpu Architecture_Avx2_Intel Pmu - Fatal编程技术网

Performance 哈斯韦尔存储器存取

Performance 哈斯韦尔存储器存取,performance,x86,cpu-architecture,avx2,intel-pmu,Performance,X86,Cpu Architecture,Avx2,Intel Pmu,我在试验AVX-AVX2指令集,以查看连续阵列上的流式处理性能。所以我有下面的例子,在这里我做基本的内存读取和存储 #include <iostream> #include <string.h> #include <immintrin.h> #include <chrono> const uint64_t BENCHMARK_SIZE = 5000; typedef struct alignas(32) data_t { double a[B

我在试验AVX-AVX2指令集,以查看连续阵列上的流式处理性能。所以我有下面的例子,在这里我做基本的内存读取和存储

#include <iostream>
#include <string.h>
#include <immintrin.h>
#include <chrono>
const uint64_t BENCHMARK_SIZE = 5000;

typedef struct alignas(32) data_t {
  double a[BENCHMARK_SIZE];
  double c[BENCHMARK_SIZE];
  alignas(32) double b[BENCHMARK_SIZE];
}
data;

int main() {
  data myData;
  memset(&myData, 0, sizeof(data_t));

  auto start = std::chrono::high_resolution_clock::now();

  for (auto i = 0; i < std::micro::den; i++) {
    for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
      myData.b[i] = myData.a[i] + 1;
    }
  }
  auto end = std::chrono::high_resolution_clock::now();
  std::cout << (end - start).count() / std::micro::den << " " << myData.b[1]
            << std::endl;
}
所以我的问题是,为什么会发生这种影响,考虑到haswell应该能够提供2*32字节的读取,并且每个周期存储32字节

编辑1

我用这段代码意识到gcc巧妙地消除了对myData.a的访问,因为它被设置为0。为了避免这种情况,我做了另一个略有不同的基准测试,其中显式地设置了

#include <iostream>
#include <string.h>
#include <immintrin.h>
#include <chrono>
const uint64_t BENCHMARK_SIZE = 4000;

typedef struct alignas(64) data_t {
  double a[BENCHMARK_SIZE];
  alignas(32) double c[BENCHMARK_SIZE];

  alignas(32) double b[BENCHMARK_SIZE];

}
data;

int main() {
  data myData;
  memset(&myData, 0, sizeof(data_t));
  std::cout << sizeof(data) << std::endl;
  std::cout << sizeof(myData.a) << " cache lines " << sizeof(myData.a) / 64
            << std::endl;
  for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
    myData.b[i] = 0;
    myData.a[i] = 1;
    myData.c[i] = 2;
  }

  auto start = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i < std::micro::den; i++) {
    for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
      myData.b[i] = myData.a[i] + 1;  
    }
  }
  auto end = std::chrono::high_resolution_clock::now();
  std::cout << (end - start).count() / std::micro::den << " " << myData.b[1]
            << std::endl;
}
答案中也指出了同样的模式,随着 数据集大小数据不再适合L1,L2成为瓶颈。是什么 另一个有趣的是,预取似乎没有帮助,而且L1未命中 大幅增加。尽管如此,我希望看到至少50%的命中率,因为每个带到一级进行读取的缓存线都会命中
对于第二次访问(每次迭代读取64字节缓存线32字节)。然而,一旦数据集溢出到二级,一级命中率似乎下降到2%。考虑到阵列实际上与一级缓存大小不重叠,这不应该是因为缓存冲突。所以这部分对我来说仍然没有意义。

执行摘要:
对于相同的基本工作负载,不同的缓存级别可以支持不同的峰值带宽,因此具有不同大小的数据集会极大地影响性能

详细解释:
考虑到哈斯韦尔(Haswell)的情况,这并不奇怪,比如说,他可以

每循环维持2次负载和1次存储

但这只是说申请L1。如果你继续读下去,你会发现L2

可以为每个周期的数据或指令缓存提供完整的64B行

由于每次迭代需要一个加载和一个存储,因此让数据集驻留在L1中将允许您享受L1带宽并可能达到每次迭代的周期吞吐量,而让数据集溢出到L2将迫使您等待更长的时间。这取决于系统中double的大小,但由于它通常是8字节,4000*2个数组*8字节=64k,这超过了大多数当前系统上的L1大小。然而,Peter Cords在评论中指出,原始代码可能优化了零数据阵列(我不确信,但这是可能的)

现在,一旦开始进入下一个缓存级别,就会发生两件事:

  • L1写回:请注意,本文没有提到写回,这是您在带宽方面必须支付的额外惩罚(从您的性能输出可以看出,尽管它看起来有点陡峭)。将数据保留在L1中意味着不必执行任何逐出操作,而在L2中保留一些数据意味着从L2读取的每一行都必须从L1中抛出一个现有行—其中一半由代码修改,并需要显式写回。这些事务必须在读取每次迭代使用的两个数据元素的值的基础上进行—记住,存储还必须首先读取旧数据,因为行的一部分未使用,需要合并

  • 缓存替换策略-请注意,由于缓存被设置为关联的,并且很可能使用LRU方案,并且由于您连续检查阵列,您的缓存使用模式可能会填充第一个关联方式,然后移动到第二个方式,依此类推-在填充最后一个方式时,如果L2中仍然需要数据(在较大的数据集情况下),您可能会从第一个方法中删除所有行,因为它们是最近使用最少的,即使这也意味着它们是您下一个要使用的行。这就是数据集大于缓存的LRU的缺点

  • 这就解释了为什么由于这种访问模式,当您超过缓存大小至少一个单向(1/8的一级缓存)时,性能会突然下降

    关于perf结果的最后一点评论——对于5000个元素的情况,您可能会预期L1命中率会下降到一个不错的零轮,我相信确实如此。然而,硬件预取可能会使您看起来仍然在L1中命中它,因为它在实际数据读取之前运行。您仍然需要等待这些预回迁来传送数据,更重要的是,因为您正在测量带宽-它们仍然占用与实际加载/存储相同的带宽,但它们不计入性能,这让您相信您一直都有L1命中率。这至少是我最好的猜测——你可以通过禁用预回迁并再次测量来检查这一点(我似乎经常给出这样的建议,很抱歉我这么拖沓)


    编辑1(以下为您的)

    消除阵列的一大亮点,它解决了双倍大小的谜团——它确实是64位的,因此一个4000元素的阵列,或者两个2000元素的阵列(修复后)都可以在L1中容纳。现在溢出发生在3000个元素处。L1命中率现在很低,因为L1无法发出足够的预回迁以在2个不同流之前运行

    至于每个加载都会在2次迭代中产生一个64字节的行的预期-我看到了一些非常有趣的事情-如果你将从内存单元发出的加载数相加(L1命中+L1未命中),你会发现2000个元素的情况几乎是1000个元素的2倍,但这3000例和4000例病例并非分别是3倍和4倍,而是一半。具体来说,每个数组有3000个元素,访问量比2000个元素少
    这使我怀疑内存单元能够将每2个加载合并到一个内存访问中,但只有在进入L2和更高级别时。这是有道理的,当你想到它,没有r
    #include <iostream>
    #include <string.h>
    #include <immintrin.h>
    #include <chrono>
    const uint64_t BENCHMARK_SIZE = 4000;
    
    typedef struct alignas(64) data_t {
      double a[BENCHMARK_SIZE];
      alignas(32) double c[BENCHMARK_SIZE];
    
      alignas(32) double b[BENCHMARK_SIZE];
    
    }
    data;
    
    int main() {
      data myData;
      memset(&myData, 0, sizeof(data_t));
      std::cout << sizeof(data) << std::endl;
      std::cout << sizeof(myData.a) << " cache lines " << sizeof(myData.a) / 64
                << std::endl;
      for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
        myData.b[i] = 0;
        myData.a[i] = 1;
        myData.c[i] = 2;
      }
    
      auto start = std::chrono::high_resolution_clock::now();
      for (auto i = 0; i < std::micro::den; i++) {
        for (uint64_t i = 0; i < BENCHMARK_SIZE; i += 1) {
          myData.b[i] = myData.a[i] + 1;  
        }
      }
      auto end = std::chrono::high_resolution_clock::now();
      std::cout << (end - start).count() / std::micro::den << " " << myData.b[1]
                << std::endl;
    }
    
    | Event          | Size=1000   | Size=2000   | Size=3000   | Size=4000     |
    |----------------+-------------+-------------+-------------+---------------|
    | Time           | 86  ns      | 166 ns      | 734 ns      | 931    ns     |
    | L1 load hit    | 252,807,410 | 494,765,803 | 9,335,692   | 9,878,121     |
    | L1 load miss   | 24,931      | 585,891     | 370,834,983 | 495,678,895   |
    | L2 load hit    | 16,274      | 361,196     | 371,128,643 | 495,554,002   |
    | L2 load miss   | 9,589       | 11,586      | 18,240      | 40,147        |
    | L1D wb acc. L2 | 9,121       | 771,073     | 374,957,848 | 500,066,160   |
    | L1D repl.      | 19,335      | 1,834,100   | 751,189,826 | 1,000,053,544 |
    
      4007a8:   48 8d 85 d0 2a fe ff    lea    -0x1d530(%rbp),%rax
      4007af:   90                      nop
      4007b0:   c5 f5 58 00             vaddpd (%rax),%ymm1,%ymm0
      4007b4:   48 83 c0 20             add    $0x20,%rax
      4007b8:   c5 fd 29 80 60 38 01    vmovapd %ymm0,0x13860(%rax)
      4007bf:   00 
      4007c0:   48 39 c2                cmp    %rax,%rdx
      4007c3:   75 eb                   jne    4007b0 <main+0x50>
      4007c5:   83 e9 01                sub    $0x1,%ecx
      4007c8:   75 de                   jne    4007a8 <main+0x48>