【问题标题】:Speeding up a do-while loop with loop unrolling使用循环展开加速 do-while 循环
【发布时间】:2019-05-25 09:26:44
【问题描述】:

我正在尝试加速可能被多次调用(可能超过一百万)的函数中的代码。该代码与将两个变量设置为随机数并找到平方距离有关。我的第一个想法是循环展开,但由于while 条件决定了我应该如何执行它,我有点困惑。

为了加快我的程序,我已经用自定义函数替换了 c++ 内置的 rand() 函数,但我不知道如何让我的程序更快。

do {
    x = customRand();
    y = customRand();
    distance = x * x + y * y; // euclidean square distance
} while (distance >= 1.0);

【问题讨论】:

  • 今天的循环展开是一种“微优化”,您的编译器能够确定在您的情况下这种优化是否有任何好处。最好专注于编写清晰、有据可查的代码,并将微优化留给编译器(它会在 10000 次中有 9999 次做得更好,而且出错的可能性要小得多。
  • 这真的取决于customRand()returns。 0 到 100 更难获得distance < 1.0,0 到 1 虽然让它“更快”。
  • 循环中有几行if (x*x+y*y<1) break;。但我怀疑它会加快执行速度。您应该比较编译器优化的代码和您自己优化的代码的运行时间。
  • 你不应该使用rand。我不想低估你的编程技能,但你确定<random> 库比你的customRand 更差吗?
  • 首先,使用<random> 并找到满足您需求的合适生成器。不要一直生成随机值,直到获得满足您要求的一对,而是以明确满足您要求的方式转换 xy 值。例如,如果您选择的生成器保证生成介于010 之间的值,只需将1 添加到xy 即可保证满足您的条件。不用计算distance,不用检查,完全不用循环。

标签: c++ do-while loop-unrolling


【解决方案1】:

您不应该期望通过循环展开来使您的程序更快,因为对于随机数生成器输出的正确选择范围 ([-1, 1]),您的循环主体将在超过 3/4 的循环中执行一次案例。

您可能想要帮助编译器的是将您的while 条件标记为“不太可能”。例如,在 GCC 中是:

#define unlikely(x) __builtin_expect((x),0)

do {
    x = customRand();
    y = customRand();
    distance = x * x + y * y; // euclidean square distance
} while (unlikely(distance >= 1.0));

不过,即使这样也不太可能以可衡量的方式加速您的代码。

如果你是关于保证时间而不是速度,那么对于一个圆圈内的均匀随机分布,customRand() 均匀分布在[-1, 1]

r = std::sqrt(std::abs(customRand()));
t = M_PI * customRand();
x = r * std::cos(t);
y = r * std::sin(t);

会成功的。

【讨论】:

  • 在这种特殊情况下,GCC 生成完全相同的汇编代码,有和没有__builtin_expect:godbolt.org/z/NWi-r9
  • 这取决于编译器是否能看到customRand()的body,以及body是什么。例如,here 它会生成不同的代码。但正如我所说,在这项特定任务中不太可能显着加快任何速度。
  • 同意。你的例子很有趣:do 循环退化为一个无限微不足道的A: jmp A;,你要么在一次执行do body 后跳入它。
【解决方案2】:

它闻起来像基于数学的XY problem,但我将首先关注您关于循环加速的问题。不过我还是会做一些数学运算。

因此,如果xy 中的任何一个大于或等于1.0,则基本加速可能会使用早期的continue。当xy 之一大于1 时,x*x + y*y < 1.0 是不可能的。

do {
    float x = customRand();
    if (x >= 1.0) {
        continue;
    }
    float y = customRand();
    if (y >= 1.0) {
        continue;
    }
    distance = x * x + y * y; // euclidean square distance
} while (distance >= 1.0);

不幸的是,它很可能会破坏现代 CPU 的分支预测机制,并且仍然可能不会带来很大的加速。一如既往,需要基准测试。

另外,由于最快的代码是不运行的,让我们讨论一下潜在的 XY 问题。您似乎生成了以半径为1.0 的圆为界的点。在笛卡尔系统中这是一项艰巨的任务,但在极坐标系统中则非常容易,其中磁盘中的每个点都用半径和角度来描述,并且可以很容易地转换为笛卡尔坐标。我不知道您期望磁盘区域的分布是什么,但请在此处查看此问题的答案:Generate a random point within a circle (uniformly)

附言。有一些关于 <random> 的有效 cmets,作为 std 库也可能会导致一些加速。

【讨论】:

    猜你喜欢
    • 2016-03-27
    • 2021-07-18
    • 2020-11-26
    • 2018-08-22
    • 2021-07-27
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    相关资源
    最近更新 更多