例如,我有三个float
数组,a
,b
和c
,我想添加a
和b
元素明智地达到c
。一种天真的方式就像
for(int i = 0; i < n; i++){
c[i] = a[i] + b[i];
}
据我所知,OpenMP
可以并行化这段代码。在OpenCV
代码中,我看到了一些与优化相关的标记CV_SSE2
和CV_NEON
。
如果我希望我的代码高效,那么优化这些代码的常用方法是什么?
答案 0 :(得分:6)
没有共同的策略。您应该确定它是一个瓶颈(如果阵列的大小n
足够小,它可能不是)。
通过使用optimize机器指令,一些编译器能够vector(至少在某些简单情况下)。使用GCC尝试使用gcc -O3 -mtune=native
(或其他-mtune=
...或-mfpu=
...参数进行编译,特别是如果您正在进行交叉编译)并且可能OpenMP 1}}
您可以考虑OpenCL,OpenACC(使用GPGPU),MPI,pthreads,使用例如显式线程C++11或std::thread numerical computation library - s等...(以及多种方法的巧妙组合)
我会将优化留给编译器,并且只考虑改进它,如果你测量它是一个瓶颈。您可以花费数月或数年(甚至专注于整个工作生活中)开发人员的时间来改进它....
您还可以使用一些LAPACK(例如GSL,Scilab等...)或专业软件,例如Octave,R,{{ 3}}等等......
答案 1 :(得分:1)
您应该继续寻找并行选项。但是对于单线程,这样做通常会更快:
int i = 0;
for (; i < n - 3; i += 4) {
c[i] = a[i] + b[i];
c[i + 1] = a[i + 1] + b[i + 1];
c[i + 2] = a[i + 2] + b[i + 2];
c[i + 3] = a[i + 3] + b[i + 3];
}
for (; i < n; i++) {
c[i] = a[i] + b[i];
}
有时展开可以由编译器完成,但至少根据我的经验(我使用MSC),编译器通常不会尝试像这样执行任何部分展开,有时它可以提供帮助。当循环中的4个内容中的每个都可以流水线化并且并行运行时,这可能是有益的,并且它可以节省比较/跳转。
所以我会以此为出发点,并对其进行衡量。然后,如果您测量增益,则仅应用并行化。或者,如果您手动创建线程,则每个线程可能应该执行展开的变体。
更新:我个人没有从中看到任何好处。我认为这是因为在展开的循环中,访问了完整的12个浮点数。并且浮动操作可能足够缓慢,以抵消通过展开而消除的jge / cmp操作的任何节省。
但是,无论何时遇到类似的问题,使用更轻松,独立的操作,我仍然建议至少尝试这一点,因为当您在代码中展开它时会生成明显不同的程序集,并且您将获得一些不同的性能特征并减少cmp / jmp的数量是4倍,这可能有所帮助,但我认为浮点运算对于此非常重要。
答案 2 :(得分:1)
正如其他人已经提到的,没有“共同策略”,但它实际上取决于您的特定用例:阵列是否非常大?它们是否相当小但你必须经常调用这个功能?这个问题你必须问自己。在尝试优化任何内容之前,您应该始终分析您的代码。在大多数应用程序中,超过90%的时间仅花费不到10%的代码。除非您确切知道在哪里找到这10%,否则对优化应用程序的各个部分几乎没有影响。
然而,当它涉及算术计算时,我认为依靠优化的标准算法始终是一个良好的开端。当关注效率时,我会添加两个数组(在将a和b放入std :: vector或std :: array并预先分配c之后)
std::transform(a.begin(), a.end(), b.begin(),c.begin(), std::plus<float>());
答案 3 :(得分:-1)
根据编译器的优化阶段,数组索引a[i]
可能比指针解除引用*p
慢(在每次迭代中p递增,因此p = a+i
)
因此,在不依赖优化器的情况下,使用某些编译器可以更快地 :
float* pa = a;
float* pb = b;
float* pc = c;
for(int i = 0; i < n; i++)
*pc++ = *pa++ + *pb++;
虽然在这种情况下看起来似乎微不足道,但这种基本技术可以在更复杂的情况下获得大量收益,在这种情况下,事情对于优化器来说太复杂了。