【问题标题】:how would you optimize this function?您将如何优化此功能?
【发布时间】:2017-02-07 20:06:42
【问题描述】:
#include <stdlib.h> 
#include <cstring.h>
#include <time.h>

int cp[1000000][3];
int p[1000000][3];//assume this array to be populated

void  main(){

srand(time(NULL));

for(n; n < 1000000; n++){
    if (rand()%2)
        memcpy(cp[n], p[n], 12);
    }
}

}

这是我正在使用的实际代码的简化版本。这段代码占据了我过程的重要部分,我想知道我是否可以用一些聪明的技巧来优化它。我以前使用过指针来避免分支,但我不知道如何在这里应用它。

【问题讨论】:

  • if ((float)rand() &gt; 0.5) 对我来说没有任何意义。 rand() 返回 0 到 RAND_MAX,所以循环唯一一次为假的时候是它返回 0。这真的是你想要的吗?
  • 忘记了 RAND_MAX。现在应该可以正确书写了。
  • 现在你遇到了(rand()/RAND_MAX) 是整数除法的问题,所以除非rand() 返回RAND_MAX,否则它将始终为0。那么它将是 1。
  • 无论如何,首先没有理由使用浮点数。
  • for(n,n&lt;1000000,n++){ 大多数人不会在这里使用逗号,而是使用分号:for(int n; n&lt;1000000; n++){

标签: c++ c optimization branch-prediction


【解决方案1】:

rand() 很可能是这段代码的瓶颈。由于您只需要二元决策,因此请考虑使用单个随机数的所有位来分摊随机数生成的成本。

for(int n=0; n<1000000; n+=NUM_BITS){
    uint32_t rand_val = static_cast<uint32_t>(rand()); // Edited based on comments
    for(int j=0; j<NUM_BITS; j++) {
        if((rand_val >> j) % 2) {
            memcpy(cp[n+j], p[n+j], 12);
        }
    }
}

唯一的窍门是从RAND_MAX 中找出NUM_BITS,并决定你想要它的高质量和便携性。选择NUM_BITS,使1&lt;&lt;NUM_BITS 小于RAND_MAX。请注意,此版本假定将 NUM_BITS 平均划分为样本总数。检查此限制或编写循环序言以适应部分内容留给 OP 练习。

我的 Linux 文档警告我,旧版本的 rand() 对数字的所有位都没有高质量的随机性,但现在已修复。如果您关心高质量的随机性,请注意这一点。

如果随机性的质量不是特别重要,您也可以寻找更快的随机生成器(它们存在)。

【讨论】:

  • rand() 通常是整数乘法和隐含模数的加法。但好主意!
  • 然而,这增加了更多的分支。您必须将rand() 造成的开销与所有丢失的分支预测进行比较,看看哪个更有效。
  • 我想cp[n+j], p[n+j] 也可能导致更糟糕的数据缓存。如果是这样,那么这可能是一个比该循环中其他任何事情都更重要的主要瓶颈。
  • Profiling 显示 rand 占原始问题运行时间的约 70%
  • 分析显示此版本将运行时间降低到原始版本的 33%。
【解决方案2】:

很难提供完整的答案。

  1. (评论)我认为rand 只是外部 50/50 决策的占位符,也不用于生产用途?

否则,请注意rand() 很烂。这有助于使数字在匆忙中看起来很随机的白痴。避免浮点除法。 rand()%2 通常比 rand()>RAND_MAX/2 差一些,但这种差异并不重要。

  1. (注释)您假设 sizeof(int)==4。不太好。

  2. 是否有理由不只复制整个缓冲区?

单个大副本可能比许多小副本快,即使它涉及双倍数据。

即如果不使用未复制的元素,则原始数据是否在其中并不重要。 OTOH,如果不能覆盖未复制的元素,则不适用。

  1. 用 3 个整数赋值替换 memcpy。

好的编译器应该能够在像你现在这样的大多数情况下做到这一点,但是 memcpy 可能会变得有点复杂。 (它需要检查奇数长度,可能需要检查未对齐的读取等)

这允许三个分配并行使用每个内核的多个单元。

  1. 并行化(但缓存)的巨大优化潜力

如果您可以使随机数生成不连续 - 例如通过使用 4 个独立的生成器 - 可以将负载分布在多个线程上,每个线程处理一大块数据。

  1. 可以通过复制到虚拟缓冲区来避免分支

这是一个有趣的想法,不过我不确定它是否会让你买太多:

int dummyBuffer[3];
for(...)
{
  int * target = (rand() % 2) ? dummyBuffer : cp+n;
  //  <-- replace with arithmetic trickery to avoid the branch
  target[0] = p[n][0];
  target[1] = p[n][1];
  target[2] = p[n][2];
}

(正如写的那样,分支将被移动到“目标”的分配,并没有太大的胜利。但是,您可能知道/可以构造一些技巧来使这个分配无分支)

【讨论】:

    【解决方案3】:

    摆脱浮点是您应该做的一项明显改进。那部分看起来很可疑,我假设您希望代码复制数据的概率为 50%?

    可以使用一些愚蠢的技巧来删除分支本身:

    int do_copy = rand() % 2;
    memcpy(cp[n], p[n], 12*do_copy);
    

    但是,如果不先查看优化代码的反汇编,我不会编写这样的代码。

    【讨论】:

    • rand() &amp; 1 会进一步改进还是% 2 一样有效?
    • &amp;1 vs. %2:现代编译器没有区别。
    • @4386427 任何体面的编译器都应该能够优化它。如果您的编译器没有对其进行优化,那么在大多数 CPU 上,位检查确实比除法快得多。
    • 计算长度而不是编译时常量实际上可能会使事情变得更糟,具体取决于 memcpy 的展开方式。
    • @peterchen 是的。如果没有考虑到特定的系统和编译器,谈论这样的优化通常是没有意义的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 2011-11-08
    • 1970-01-01
    相关资源
    最近更新 更多