1

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

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(function A) 和CalcResult f_B(function B),Test 是:

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是唯一合理的方法,因为它使用更少的内存。)

用替代品替换 c-style xyzarraysstd::vector<double>是否有可能以任何方式加速计算或内存访问?

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

4

2 回答 2

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).

于 2013-09-23T23:50:36.090 回答
1

经典 SIMD 编译器优化

编译器使用 SIMD 指令轻松优化的已知代码的简单示例如下:

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

使用 VC++ 的 SIMD 优化示例

在你的情况下

您的第一个循环c.GetDist看起来确实所有迭代都是相互独立的,但取决于GetDist实际情况,再加上将结果推回向量,我认为编译器生成 SIMD 指令可能比生成 SIMD 指令更难只需在内置数组中添加 2 个向量即可。不过,我不是编译器专家,所以我可能是错的。它也可能因编译器而异。

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

英特尔汇编语言参考

有趣的谈话

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

Jim Radigan 谈论编译器优化

于 2013-09-24T00:24:44.293 回答