速度比较:先加零或先检查非零

时间:2019-02-09 22:44:30

标签: c++ optimization

我正在优化我正在编写的程序中最耗时的循环,该循环将数组中的许多项加起来,其中许多将为零。是在添加条目之前检查条目是否为零,还是跳过检查并添加所有条目的速度更快?下面的每个示例。这是在C ++中。谢谢!

double *arr, sum=0;
...
for (int i = 0; i < n; i++)
    sum += arr[i];

OR

double *arr, sum=0;
...
for (int i = 0; i < n; i++)
    if (arr[i])
        sum += arr[i];

4 个答案:

答案 0 :(得分:2)

当天的报价:

  

过早的优化是万恶之源
  -唐纳德​​·克努斯(Donald Knuth)

如果您的目的是添加数组的所有元素,请编写恰好做到这一点的代码,并让编译器的优化器照顾到最好的东西。因此,寻求第一种选择;你的未来,你将感恩的一天。

如果不是绝对必要,请不要进行手动优化:

对于现代CPU,无论如何都很难想到缓存管理,缓存优化,跳转预测和其他硬件技巧的所有可能影响。编译器的优化器可以组合比我们更多的因素。

如果您确实发现了一些性能问题,请分析您的代码,并将精力集中在真正重要的优化上。另外,您可以在目标平台上对代码进行基准测试,但要注意基准测试中的细微差别,这可能会影响优化器。

现在,这就是说,第二个选项要求对数组中的每个项目都使用比较指令(x86上的ucomisd)。因此,如果大多数项目的值都不为零,则通常会增加不必要的开销。对于空项目,您将交换带有两个指令的简单加法:比较和条件分支。我不确定这是否真的更快,但是如果有任何好处,那将是非常微不足道的。因此,在最佳情况下,您可以获得很小的收益,但是很可能会增加一些开销。直观地讲,请坚持第一种选择,除非您的分析器告诉您有问题。

答案 1 :(得分:2)

如果您在Intel体系结构上运行,则可以采用一种方法来加快此速度,但这并不是一件很漂亮的事情:您使用REPZ SCASD指令扫描数组中的下一个非零元素。当然,您将需要使用汇编语言对此进行编程。并且它依赖于数组的大多数零元素表示为0x0000000000000000,尽管可能无法保证确实如此。

如果要实现这一点,我将用汇编语言编写一个可调用C的函数:

size_t NextNonZeroArrayElement (double* arr, size_t len)

仅当个元素为零时(不只是其中许多个为零),这才是值得的。但是无论如何,如果有时间,这是一个有趣的项目。

如果您真的很热心,则可以考虑使用汇编语言编写整个内容,并完成浮点运算。然后我认为您会以较低的零元素比例领先。

答案 2 :(得分:2)

对于现代CPU(假定阵列大小“不可忽略”);最快的选择是使用SIMD。例如,也许是一个小的“开始循环”,该循环开始处理数组的各个元素,直到满足任何对齐要求为止,然后是一个使用AVX2的“中间循环”(其中可以并行完成一组8个int添加)通过单个指令),然后可能是一个小的“结束循环”,该循环执行的数组的所有最终元素不足以成为8个一组。对于这些小的循环(在开始/结束时),不可预测的分支是令人讨厌的,并且可以成本是简单加法的10倍,但是可预测的分支很好,所以这取决于零发生的可预测性。

当然,对于非常大的阵列,您还希望使用多个CPU-例如使用“低端” 4核芯片(和SIMD),您可以将阵列分成四分之一,并并行进行4 * 8整数加法运算。

对于完美的编译器而言,您在C源代码中编写的内容不会有任何区别-生成的程序集将与“使用SIMD的CPU最快的选项”相同。

除此以外,还取决于您的编译器没有达到理想的程度。大多数编译器无法自动向量化。大多数编译器不会意识到if(x) sum += x;等同于sum += x;。没有编译器会为您创建线程。您需要进行分析以确定编译器失败的原因,然后花额外的时间(用线程和内在函数)重写代码,因为您应该“过早地预见”编译器会烂掉。

答案 3 :(得分:0)

感谢您的输入。对于那些发现了这个问题并且正在做类似事情的人,我进行了一些分析,发现两者具有基本上相同的运行时。

我在Linux的Windows子系统上进行了优化并运行。

还可以进行进一步优化: 有趣的是,如果内部操作如下所示,则添加if语句将运行时间减少近一半。另外,如果您要检查一个指示符变量,然后添加一个指示符变量,则添加if语句会使运行时间大致加倍,大概是由于内存移动量增加了一倍。两者在两个数组中都有10%的非零条目。

使用if语句更快:

double *arr, sum=0;
int *arri;
...
for (int i = 0; i < n; i++)
    if (arri[i])
        sum += arr[i] * arri[i];

不使用if语句(假设arri [i] == 0意味着arr [i] == 0)更快:

double *arr, sum=0;
int *arri;
...
for (int i = 0; i < n; i++)
    if (arri[i])
        sum += arr[i];