在STL矢量上重复操作是否允许“固有并行”/改进内存访问?

时间:2013-09-23 22:27:16

标签: c++ vector stl 3d parallel-processing

我有一个两阶段过程,在我的模拟程序中构成一个循环。或多或少我有以下内容:

struct Coordinates
{
   double * x, * y, * z;
   uint * kind, count;
   double GetDist(const uint p1, const uint p2);
};

struct Polynomial
{
   double * A, * B;
   uint n1, n2;
   uint Flatten(const uint i, const uint j);
   double CalcResult(double distSq, uint kind1, uint kind2)
   {
      uint ij = Flatten(kind1, kind2);
      double base = B * distSq;
      return A[ij]*(pow(base,n2)-pow(base,n1));
   }
};

我的问题是我是否编写了像

这样的代码
struct Model
{
   Coordinates c;
   Polynomial f;
   double DoTest()
   {
      double result = 0;
      uint count = 0;
      std::vector<double> distSq;
      for (uint i=0; i<c.count; i++)
      {
         for (uint j=i; j<c.count; j++)
         {
            result = c.GetDist(i,j);
            distSq.push_back(result);
         }
      }
      result = 0;
      for (uint i=0; i<c.count; i++)
      {
         for (uint j=i; j<c.count; j++)
         {
            result += f.CalcResult(distSq[count], i, j);
            count++;
         }
      }
      return result;
   }
   double DoTest2()
   {
      double result = 0;
      for (uint i=0; i<c.count; i++)
         for (uint j=i; j<c.count; j++)
            result += f.CalcResult(c.GetDist(i,j), i, j);
      return result;
   }
}

考虑到在单个数据集上重复操作,Test会自动在x86芯片上启用并行性(例如矢量化数学或改进的内存访问)吗?

否则Test是一种垃圾方法 - 它使用额外的存储空间(std::vector<double> distSq;),并且在代码读取方面要长得多。从逻辑上讲,它或多或少相同,但如果我们调用GetDist f_A(函数A)和CalcResult f_B(函数B),则测试为:

f_A f_A f_A ... f_A    f_B f_B .... f_B

更短/更少内存密集型功能

f_A f_B f_A f_B .... f_A f_B

由于生成的矢量化数学运算等,我在-O#编译的C代码中听说过所谓的“固有并行性”。可Test启用这种编译器派生的并行性(例如矢量化)算术或优化内存访问?)在x86芯片上,考虑到它在单个数据集上的重复操作?

(否则Test2是唯一合理的方法,因为它使用更少的内存。)

同样用x替代方案替换c风格的yzstd::vector<double>数组是否有可能以任何方式加速计算或内存访问?

请不要回答“自己进行基准测试”...我之所以要求尝试通过基于理论视角的基准测试来更好地理解是否值得测试方法Test编译器和“固有的并行性”。

2 个答案:

答案 0 :(得分:1)

无论并行性如何,内存访问都会杀死你。在.reserve(c.count*c.count())调用.push_back以阻止重新分配时,会有一些小改进,但这还不够。如果c.count足够重要,这将浪费L1缓存和可能的L2。

下一个问题是你的f_A函数取决于内存访问。现代处理器可以同时发布读取和处理先前f_B的处理器。没有数据依赖性。这使Test2更有效率。

BYW,它只是我,还是CalcResult(i,j)和CalcResult(j,i)非常相似?您可能会从计算结合中受益。

我会AB double const*。毕竟,你不是在写它们。

可能效果很好的是#pragma omp for reduction(+, result)

答案 1 :(得分:1)

经典SIMD编译器优化

编译器使用SIMD指令很容易优化的代码的简单示例如下:

for (int i = 0; i < N; ++i) 
     C[i] = A[i] + B[i];

SIMD optimization example with VC++

在你的情况下

你的c.GetDist的第一个循环确实看起来所有迭代都是彼此独立的,但是根据GetDist真正做的事情,结合将结果推回到向量中,我认为它可以编译器生成SIMD指令比在内置数组中简单地添加2个向量更难。不过,我不是编译专家,所以我错了。它也可能因编译器而异。

确定的最佳方法是编译代码并查看反汇编以查看编译器生成的指令类型。例如,如果您使用的是IA-32或64位Intel,请查找作用于MMX或XMM寄存器的指令。您也可以尝试使用内置数组替换向量,看看它是否有任何区别。

Intel assembly language reference

有趣的谈话

我最近观看了Jim Radigan在Going Native 2013大会上的有趣演讲。他在Microsoft C ++编译器后端团队工作,专门从事代码优化。他谈到了几个有趣的话题,其中包括在生成的机器代码中实现并行性。这是谈话的链接:

Jim Radigan talks about compiler optimization