我认为通常在C ++中使用这样的代码
for(size_t i=0;i<ARRAY_SIZE;++i)
A[i]=B[i]*C[i];
一个普遍提倡的变更是:
double* pA=A,pB=B,pC=C;
for(size_t i=0;i<ARRAY_SIZE;++i)
*pA++=(*pB++)*(*pC++);
我想知道的是,改进此代码的最佳方法,因为IMO需要考虑以下事项:
任何建议都将不胜感激!
答案 0 :(得分:5)
我的g ++ 4.5.2为两个循环生成完全相同的代码(修复了double *pA=A, *pB=B, *pC=C;
中的错误,它是
.L3:
movapd B(%rax), %xmm0
mulpd C(%rax), %xmm0
movapd %xmm0, A(%rax)
addq $16, %rax
cmpq $80000, %rax
jne .L3
(我的ARRAY_SIZE为10000)
编译器作者已经知道了这些技巧。但是,OpenMP和其他并发解决方案值得研究。
答案 1 :(得分:4)
性能规则是
尚未
获得目标
测量
了解可能有多少改进,并确认花时间去获取它是值得的。
现代处理器更是如此。关于你的问题:
指针映射的简单索引通常由编译器完成,当他们不这样做时,他们可能有充分的理由。
处理器通常已经过优化,可以顺序访问缓存:简单的代码生成通常可以提供最佳性能。
SSE也许可以改善这一点。但如果你的带宽有限,那就不行了。所以我们回到测量并确定边界阶段
并行化:与SSE相同。如果带宽有限,使用单个处理器的多个内核将无济于事。根据内存架构,使用不同的处理器可能有所帮助。
手动循环展开(在现在删除的答案中建议)通常是一个坏主意。编译器知道如何在价值方面做到这一点(例如,如果它可以进行软件流水线操作),而现代OOO处理器通常不是这样(它增加了指令和跟踪缓存的压力,同时执行OOO,猜测跳转和注册重命名将自动带来展开和软件流水线的大部分好处。
答案 2 :(得分:2)
第一种形式正是编译器将识别和优化的结构,几乎肯定会自动发出SSE指令。
对于这种琐碎的内部循环,缓存效果是无关紧要的,因为你正在遍历所有内容。如果您有嵌套循环或一系列操作(如g(f(A,B),C)),那么您可能会尝试重复访问小块内存以更加缓存。
不手动展开循环。如果这是一个好主意(它可能不在现代CPU上),你的编译器也会这样做。
如果循环很大并且内部的操作足够复杂,以至于你还没有内存限制,那么OpenMP可能会有所帮助。通常,以自然而直接的方式编写代码,因为这是优化编译器最有可能理解的内容。
答案 3 :(得分:1)
何时开始考虑SSE或OpenMP?如果这两个都是真的:
for (size_t i = 0; i < ARRAY_SIZE; ++i)
{A[i] = B[i] * C[i];
通常大于1000万,或者,如果探查者告诉您此操作正在成为瓶颈然后,
ARRAY_SIZE
void array_mul(double* pa, const double* pb, const double* pc, size_t count)
作为旁注,如果你有很多操作只比这稍微复杂一点,例如{ for (...) }
然后支持expression template的图书馆也会有用。
答案 4 :(得分:0)
您可以使用一些简单的并行化方法。 Cuda将依赖于硬件,但SSE几乎是每个CPU的标准配置。您也可以使用多个线程。在多个线程中,您仍然可以使用指针技巧,这不是很重要。这些简单的优化也可以由编译器完成。如果您使用的是Visual Studio 2010,则可以使用parallel_invoke并行执行函数,而无需处理Windows线程。在Linux中,pThread库非常易于使用。
答案 5 :(得分:0)
我认为使用valarray专门用于此类计算。我不确定它是否会改善性能。