2011-06-08 62 views
1

我相信这是通常在C具有这样的代码++阵列C [] = A [] * B []在高性能计算

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需要考虑的事情:

  1. CPU缓存。 CPU如何填满他们的缓存以获得最佳命中率?
  2. 我想SSE可以改善呢?
  3. 另一件事是,如果代码可以并行化呢?例如。使用OpenMP。在这种情况下,指针技巧可能不可用。

任何建议,将不胜感激!

+0

IMO,你的编译器至少应该做一个相当不错的工作来优化你的缓存访问代码。 – zneak 2011-06-08 05:03:45

+0

在第二个循环中,为什​​么当你有指针时仍然使用'i'? '我'没有在那里使用。 – iammilind 2011-06-08 05:04:46

+0

@iammilind我用来计算数组的个数。反正它不是一个字符串数组。 – xis 2011-06-08 05:07:56

回答

4

性能的规则是

  1. 尚未

  2. 得到目标

  3. 措施

  4. 得到多少改善是可能的一个想法,验证它是值得花时间去获得它。

这对现代处理器来说更是如此。关于你的问题:

  1. 简单的索引指针映射往往是由编译器完成的,而当他们不这样做,他们可能有很好的理由。

  2. 处理器已经经过优化以顺序访问缓存:简单的代码生成通常会提供最佳的性能。

  3. 上证所可以改善这一点。但是,如果你已经受到带宽限制,则不行所以我们回到了测量和确定边界阶段

  4. 并行:与SSE相同的东西。如果带宽有限,使用单个处理器的多个内核将无济于事。取决于内存架构,使用不同的处理器可能会有帮助

  5. 手动循环展开(在现在删除的答案中建议)往往是一个坏主意。编译器在知道如何做到这一点时(例如它可以进行软件流水线处理),并且使用现代的OOO处理器通常不是这种情况(它增加了执行OOO指令和跟踪高速缓存时的压力,跳转和寄存器重命名将自动带来解绕和软件流水线的大部分好处)。

5

我克++ 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和其他并发解决方案值得研究

+0

哈哈我总是有使用TYPE *的麻烦。无论如何,好评! – xis 2011-06-08 05:12:08

2

第一种形式正是你的编译器会识别和优化的结构,几乎肯定会发出SSE指令automa的角度讲。

对于这种微不足道的内部循环,缓存效果是无关紧要的,因为您正在遍历所有内容。如果你有嵌套循环或一系列操作(比如g(f(A,B),C)),那么你可以试着重复访问小块内存,以便更容易缓存。

不是手动展开循环。如果它是一个好主意(它可能不在现代CPU上),你的编译器也已经这样做了。

如果循环很庞大,并且内部操作很复杂,以致于您尚未受内存限制,OpenMP可能会有所帮助。

通常,以自然直接的方式编写代码,因为这是您的优化编译器最可能理解的。

+0

如果我认为指针版本*是最自然直接的呢? – 2011-06-08 06:03:40

+0

@比利:那你从来没有用C/C++以外的语言编程过?严重的是,旋转指针更容易混淆编译器(和人类阅读器),而不是简单的数组引用。 – Nemo 2011-06-08 06:04:23

+0

我用很多语言编程。 (这并不意味着我不喜欢C++更多)我经常使用指针迭代,因为我习惯用C++编写STL泛型代码。但是这不是我评论的重点 - 我的观点是,你的答案的大部分斜体部分应该是“以自然直接的方式编写代码”,而不是“不要使用指针版本”。我同意在这种情况下,索引可能更清晰,但并非总是如此。我看到了那些没有使用讨厌的索引数学等方法试图解决它的指针的人引起的太多讨厌的C和C++。 – 2011-06-08 06:08:10

0

您可以使用一些简单的并行化方法。 Cuda将取决于硬件,但SSE几乎是每个CPU的标准配置。你也可以使用多个线程。在多线程中,你仍然可以使用不重要的指针技巧。这些简单的优化也可以通过编译器完成。如果您使用的是Visual Studio 2010,则可以使用parallel_invoke并行执行函数,而无需处理Windows线程。在Linux中,pThread库非常易于使用。

+0

大多数* x86 * CPU。 MIPS/ARM机器没有SSE。 – 2011-06-08 06:04:04

+0

你是对的。 – 2011-06-09 20:36:09

1

何时开始考虑SSE或OpenMP?如果这两个都为真:

  • 如果发现代码类似于你在你的项目中出现20次以上:
    for (size_t i = 0; i < ARRAY_SIZE; ++i)
    A[i] = B[i] * C[i];
    或一些类似的操作
  • 如果ARRAY_SIZE是经常比我的大10万元,或者,如果分析器告诉你,这个操作是成为瓶颈

然后,

  • 首先,使之成为一个功能:
    void array_mul(double* pa, const double* pb, const double* pc, size_t count)
    { for (...) }
  • 第二,如果你能负担得起找到一个合适的SIMD库,改变你的函数中使用它。

作为一个侧面说明,如果你有很多是只比这个,例如稍微更复杂的操作A[i] = B[i] * C[i] + D[i]那么支持expression template的库也会有用。

0

我认为使用valarrays专门用于这样的计算。我不确定它是否会改善性能。

相关问题