6

我敢肯定这已经被回答过,但我找不到一个很好的解释。

我正在编写一个图形程序,其中管道的一部分将体素数据复制到 OpenCL 页面锁定(固定)内存。我发现这个复制过程是一个瓶颈,并对一个简单的std::copy. 数据是浮动的,我要复制的每个数据块的大小约为 64 MB。

这是我的原始代码,在任何基准测试之前:

std::copy(data, data+numVoxels, pinnedPointer_[_index]);

其中data是浮点指针,numVoxels是无符号整数,并且pinnedPointer_[_index]是引用固定 OpenCL 缓冲区的浮点指针。

由于我的性能很慢,我决定尝试复制较小部分的数据,看看我得到了什么样的带宽。我使用 boost::cpu_timer 进行计时。我已经尝试运行了一段时间,平均运行了几百次,得到了类似的结果。以下是相关代码以及结果:

boost::timer::cpu_timer t;                                                    
unsigned int testNum = numVoxels;                                             
while (testNum > 2) {                                                         
  t.start();                                                                  
  std::copy(data, data+testNum, pinnedPointer_[_index]);                      
  t.stop();                                                                   
  boost::timer::cpu_times result = t.elapsed();                               
  double time = (double)result.wall / 1.0e9 ;                                 
  int size = testNum*sizeof(float);                                           
  double GB = (double)size / 1073741842.0;                                    
  // Print results  
  testNum /= 2;                                                               
}

Copied 67108864 bytes in 0.032683s, 1.912315 GB/s
Copied 33554432 bytes in 0.017193s, 1.817568 GB/s
Copied 16777216 bytes in 0.008586s, 1.819749 GB/s
Copied 8388608 bytes in 0.004227s, 1.848218 GB/s
Copied 4194304 bytes in 0.001886s, 2.071705 GB/s
Copied 2097152 bytes in 0.000819s, 2.383543 GB/s
Copied 1048576 bytes in 0.000290s, 3.366923 GB/s
Copied 524288 bytes in 0.000063s, 7.776913 GB/s
Copied 262144 bytes in 0.000016s, 15.741867 GB/s
Copied 131072 bytes in 0.000008s, 15.213149 GB/s
Copied 65536 bytes in 0.000004s, 14.374742 GB/s
Copied 32768 bytes in 0.000003s, 10.209962 GB/s
Copied 16384 bytes in 0.000001s, 10.344942 GB/s
Copied 8192 bytes in 0.000001s, 6.476566 GB/s
Copied 4096 bytes in 0.000001s, 4.999603 GB/s
Copied 2048 bytes in 0.000001s, 1.592111 GB/s
Copied 1024 bytes in 0.000001s, 1.600125 GB/s
Copied 512 bytes in 0.000001s, 0.843960 GB/s
Copied 256 bytes in 0.000001s, 0.210990 GB/s
Copied 128 bytes in 0.000001s, 0.098439 GB/s
Copied 64 bytes in 0.000001s, 0.049795 GB/s
Copied 32 bytes in 0.000001s, 0.049837 GB/s
Copied 16 bytes in 0.000001s, 0.023728 GB/s

在复制 65536-262144 字节的块时有一个明显的带宽峰值,并且带宽比复制整个阵列高得多(15 对 2 GB/s)。

知道了这一点,我决定尝试另一件事并复制整个数组,但使用重复调用来std::copy处理每个调用只处理数组的一部分。尝试不同的块大小,这些是我的结果:

unsigned int testNum = numVoxels;                                             
unsigned int parts = 1;                                                       
while (sizeof(float)*testNum > 256) {                                         
  t.start();                                                                  
  for (unsigned int i=0; i<parts; ++i) {                                      
    std::copy(data+i*testNum, 
              data+(i+1)*testNum, 
              pinnedPointer_[_index]+i*testNum);
  }                                                                           
  t.stop();                                                                   
  boost::timer::cpu_times result = t.elapsed();                               
  double time = (double)result.wall / 1.0e9;                                  
  int size = testNum*sizeof(float);                                           
  double GB = parts*(double)size / 1073741824.0;                              
  // Print results
  parts *= 2;                                                                 
  testNum /= 2;                                                               
}      

Part size 67108864 bytes, copied 0.0625 GB in 0.0331298s, 1.88652 GB/s
Part size 33554432 bytes, copied 0.0625 GB in 0.0339876s, 1.83891 GB/s
Part size 16777216 bytes, copied 0.0625 GB in 0.0342558s, 1.82451 GB/s
Part size 8388608 bytes, copied 0.0625 GB in 0.0334264s, 1.86978 GB/s
Part size 4194304 bytes, copied 0.0625 GB in 0.0287896s, 2.17092 GB/s
Part size 2097152 bytes, copied 0.0625 GB in 0.0289941s, 2.15561 GB/s
Part size 1048576 bytes, copied 0.0625 GB in 0.0240215s, 2.60184 GB/s
Part size 524288 bytes, copied 0.0625 GB in 0.0184499s, 3.38756 GB/s
Part size 262144 bytes, copied 0.0625 GB in 0.0186002s, 3.36018 GB/s
Part size 131072 bytes, copied 0.0625 GB in 0.0185958s, 3.36097 GB/s
Part size 65536 bytes, copied 0.0625 GB in 0.0185735s, 3.365 GB/s
Part size 32768 bytes, copied 0.0625 GB in 0.0186523s, 3.35079 GB/s
Part size 16384 bytes, copied 0.0625 GB in 0.0187756s, 3.32879 GB/s
Part size 8192 bytes, copied 0.0625 GB in 0.0182212s, 3.43007 GB/s
Part size 4096 bytes, copied 0.0625 GB in 0.01825s, 3.42465 GB/s
Part size 2048 bytes, copied 0.0625 GB in 0.0181881s, 3.43631 GB/s
Part size 1024 bytes, copied 0.0625 GB in 0.0180842s, 3.45605 GB/s
Part size 512 bytes, copied 0.0625 GB in 0.0186669s, 3.34817 GB/s

似乎减小块大小实际上具有显着效果,但我仍然无法接近 15 GB/s。

我运行 64 位 Ubuntu,GCC 优化没有太大区别。

  1. 为什么数组大小会以这种方式影响带宽?
  2. OpenCL 固定内存是否起作用?
  3. 优化大数组副本的策略是什么?
4

1 回答 1

7

我很确定您遇到了缓存抖动。如果你用你写的数据填充缓存,下一轮,需要一些数据,缓存必须从内存中读取数据,但首先它需要在缓存中找到一些空间 - 因为所有数据 [或者至少其中很多]是“脏的”,因为它已被写入,需要写入 RAM。接下来,我们将一个新数据写入缓存,这会抛出另一个脏数据(或者我们之前读过的数据)。

在汇编程序中,我们可以通过使用“非临时”移动指令来克服这个问题。以 SSE 指令movntps为例。该指令将“避免将内容存储在缓存中”。

编辑:您还可以通过不混合读取和写入来获得更好的性能 - 使用 4-16KB 的小缓冲区 [固定大小数组],并将数据复制到该缓冲区,然后将该缓冲区写入您想要的新位置。同样,理想情况下使用非临时写入,因为即使在这种情况下也会提高吞吐量 - 但仅使用“块”读取然后写入,而不是读取一个,写入一个,会更快。

像这样的东西:

   float temp[2048]; 
   int left_to_do = numVoxels;
   int offset = 0;

   while(left_to_do)
   {
      int block = min(left_to_do, sizeof(temp)/sizeof(temp[0]); 
      std::copy(data+offset, data+offset+block, temp);                      
      std::copy(temp, temp+block, pinnedPointer_[_index+offet]);                      
      offset += block;
      left_to_do -= block;
   }

试试看,看看它是否能改善情况。它可能不会...

Edit2:我应该解释一下,这更快,因为您每次都重复使用相同的缓存位来加载数据,并且通过不混合读取和写入,我们可以从内存本身获得更好的性能。

于 2013-05-20T21:15:59.343 回答