我有一个两阶段过程,在我的模拟程序中构成一个循环。或多或少我有以下内容:
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风格的y
,z
和std::vector<double>
数组是否有可能以任何方式加速计算或内存访问?
请不要回答“自己进行基准测试”...我之所以要求尝试通过基于理论视角的基准测试来更好地理解是否值得测试方法Test
编译器和“固有的并行性”。
答案 0 :(得分:1)
无论并行性如何,内存访问都会杀死你。在.reserve(c.count*c.count())
调用.push_back
以阻止重新分配时,会有一些小改进,但这还不够。如果c.count
足够重要,这将浪费L1缓存和可能的L2。
下一个问题是你的f_A
函数取决于内存访问。现代处理器可以同时发布读取和处理先前f_B
的处理器。没有数据依赖性。这使Test2
更有效率。
我会A
和B
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 ++编译器后端团队工作,专门从事代码优化。他谈到了几个有趣的话题,其中包括在生成的机器代码中实现并行性。这是谈话的链接: