我有一个两阶段的过程,在我的模拟程序中构成了一个循环。 我或多或少有以下几点:
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;
}
}
鉴于 x86 芯片上的重复操作,Test
是否会在 x86 芯片上自动启用并行性(例如矢量化数学或改进的内存访问)?
否则Test
是一种垃圾方法——它使用额外的存储空间(std::vector<double> distSq;
),并且在代码读取方面要长得多。 从逻辑上讲,它或多或少是相同的,但是如果我们调用GetDist
f_A
(函数 A)和CalcResult
f_B
(函数 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 代码中谈论所谓的"固有并行性"。 鉴于x86芯片上的重复操作,Test
能否在x86芯片上启用这种编译器派生的并行性(例如矢量化数学或优化的内存访问?
(否则Test2
是唯一合理的方法,因为它使用较少的内存。
另外,用std::vector<double>
的替代方案替换c风格的x
、y
和z
数组是否有可能以任何方式加速计算或内存访问?
请不要回答"对自己进行基准测试"...我要求尝试更好地理解是否值得从理论角度测试方法Test
,基于编译器和"固有并行性"。
无论并行性如何,内存访问都会杀死您。有一个小的改进,你打电话给.reserve(c.count*c.count())
以防止.push_back
中的重新分配,但这还不够。如果c.count
足够大,这将浪费 L1 缓存和可能的 L2。
下一个问题是您的f_A
函数依赖于内存访问。现代处理器可以同时发出读取和处理以前的f_B
。没有数据依赖关系。这使得Test2
更加高效。
BYW,是只有我一个人,还是CalcResult(i,j)和CalcResult(j,i)非常相似?合并计算可能会让您受益。
我会做A
和B
double const*
.毕竟,你不是在写它们。
可能行之有效的是#pragma omp for reduction(+, result)
.
经典 SIMD 编译器优化
已知编译器使用 SIMD 指令轻松优化的代码示例如下:
for (int i = 0; i < N; ++i)
C[i] = A[i] + B[i];
使用 VC++ 的 SIMD 优化示例
在您的情况下
使用 c.GetDist
的第一个循环看起来所有迭代都彼此独立,但根据GetDist
的实际作用,再加上将结果推回向量,我认为编译器生成 SIMD 指令可能比简单地在内置数组中添加 2 个向量更难。不过,我不是编译器专家,所以我可能是错的。它也可能因编译器而异。
确定的最好方法是编译代码并查看反汇编,看看编译器生成了哪种指令。例如,如果您使用的是 IA-32 或 64 位英特尔,请查找作用于 MMX 或 XMM 寄存器的指令。您也可以尝试用内置数组替换向量,看看它是否有任何区别。
英特尔汇编语言参考
一个有趣的谈话
我最近看了吉姆·拉迪根(Jim Radgan)在2013年本土化会议上的有趣演讲。他在Microsoft C++编译器后端团队工作,专门从事代码优化。他谈到了几个有趣的主题,其中包括在生成的机器代码中实现并行性。以下是演讲的链接:
吉姆·拉迪根谈编译器优化