优化数组的 c++ 算术计算的常用策略是什么?

What's the common strategy to optimize c++ arithmetic computation for arrays?

比如我有三个float数组,abc,我想加上ab 元素明智地高达 c。一个天真的方式就像

for(int i = 0; i < n; i++){
    c[i] = a[i] + b[i];
}

据我所知,OpenMP可以并行化这段代码。在 OpenCV 代码中,我看到一些与优化相关的标志,例如 CV_SSE2CV_NEON

如果我希望我的代码高效,优化这类代码的常用方法是什么?

没有共同的策略。你应该确定它是一个瓶颈(如果你的数组的大小 n 足够小,它可能不是)。

一些编译器能够 optimize that (at least in some simple cases) by using vector machine instructions. With GCC 尝试使用 gcc -O3 -mtune=native(或其他 -mtune=... 或 -mfpu=... 参数进行编译,特别是如果您正在交叉编译)并且可能 -ffast-math

您可以考虑 OpenMP, OpenCL (with a GPGPU), OpenACC, MPI, explicit threading with e.g. pthreads or C++11 std::thread-s 等...(以及多种方法的巧妙组合)

我会把优化留给编译器,只有在你认为它是一个瓶颈时才考虑改进它。您可以花费数月或数年(甚至整个工作生涯都专注于此)的开发人员时间来改进它....

您还可以使用一些 numerical computation library (e.g. LAPACK, GSL, etc...) or specialized software like Scilab, Octave, R,等等...

另读http://floating-point-gui.de/

根据编译器的优化阶段,数组索引 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++;

虽然在这种情况下它可能看起来微不足道,但这种基本技术可以在更复杂的情况下带来巨大的收益,在这种情况下,事情太复杂了,优化器无法完成这项工作。

您应该继续研究并行选项。但是对于单线程来说,这样操作一般会更快:

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 个事物中的每一个都可以被流水线化并且 运行 并行并且它节省 comparisons/jumps.

时,这可能是有益的

所以我会以此为起点,并对其进行测量。然后,只有在测量增益时才应用并行化。或者,如果您手工制作线程,则每个线程都应该执行展开的变体。

更新:我个人并没有从中看到任何好处。我认为这是因为在展开的循环中,访问了完整的 12 个浮点数。并且浮动操作可能足够慢以抵消通过展开它消除的 jge/cmp 操作的任何节省。

不过,每当您遇到类似的问题,使用更轻量级的独立操作时,我仍然建议您至少尝试这个,因为当您在代码中展开它时,它会生成明显不同的程序集,并且您会获得一些不同的性能特征并将 cmp/jmp 的数量减少 4 倍,这会有所帮助,但我认为浮点运算太重要了,所以这里不重要。

正如其他人已经提到的,没有 "common strategy" 但这实际上取决于您的特定用例:数组是否非常大?它们很小,但您必须非常频繁地调用此函数吗?这样的问题你将不得不问自己。在尝试优化任何东西之前,您应该始终分析您的代码。在大多数应用程序中,超过 90% 的时间只花在不到 10% 的代码上。除非您确切知道在哪里可以找到这 10%,否则它对优化应用程序的某些部分几乎没有影响。

但是,当涉及算术计算时,我认为依靠优化的标准算法总是一个好的开始。当关心效率时,我会添加两个数组(在将 a 和 b 放入 std::vector 或 std::array 并预分配 c 之后)通过

std::transform(a.begin(), a.end(), b.begin(),c.begin(), std::plus<float>());