在 C++ 中加速双精度绝对值

Posted

技术标签:

【中文标题】在 C++ 中加速双精度绝对值【英文标题】:Speeding up double absolute value in C++ 【发布时间】:2013-05-23 02:07:57 【问题描述】:

我正在分析我的代码并尽可能优化一切,最终得到一个看起来像这样的函数:

double func(double a, double b, double c, double d, int i)
    if(i > 10 && a > b || i < 11 && a < b)
        return abs(a-b)/c;
    else
        return d/c;

在程序运行期间它被调用了数百万次,分析器向我显示,大约 80% 的时间都花在了调用 abs() 上。

    我用fabs() 替换了abs(),它提高了大约10% 的速度,这对我来说没有多大意义,因为我多次听说它们对于浮点数是相同的,abs() 应该一直使用。这是不真实的还是我遗漏了什么?

    评估可进一步提高性能的双精度绝对值的最快方法是什么?

如果这很重要,我在 linux X86_64 上使用 g++

【问题讨论】:

如果 IEEE,并且您想作恶并利用未定义的行为,您可以尝试double x = a - b; *(uint64_t *)&amp;x &amp;= ~(1ULL &lt;&lt; 63); return x / c; 只是好奇 - a 是否永远等于 b? @TonyD 假设不,这对我来说没关系。 【参考方案1】:

执行所有 3 次计算。将结果粘贴到 3 元素数组中。使用非分支算法找到正确的数组索引。返回该结果。

即,

bool icheck = i > 10;
bool zero = icheck & (a > b);
bool one = !icheck & (b > a);
bool two = !zero & !one;
int idx = one | (two << 1);
return val[idx];

val 保存三个计算的结果。使用&amp; 而不是&amp;&amp; 很重要。

这消除了您的分支预测问题。最后,确保循环代码可以看到实现,这样就可以消除调用开销。

【讨论】:

我很好奇:分支预测问题相对经常出现,这种“非分支”选择技巧很经典,但即使在这种(诚然)简单的情况下,编译器似乎也没有对其进行优化. 公平地说,如果分支是可预测的,非优化版本可能会更快。除非我对其进行测试,否则我不相信 av=bove 代码会更快:我正在做 2-3 倍的工作! 是的,可能,但这正是您希望编译器介入并根据目标架构等进行优化的地方... @MatthieuM。这不仅仅是目标架构——编译器可能不知道输入是否以i &gt; 10 &amp;&amp; a &gt; b || i &lt; 11 &amp;&amp; a &lt; b 将它们分为四个(甚至两个)的顺序提供,这样分支预测几乎可以完美地工作,或者输入有有效地随机关系到测试。在这种情况下,运行时测量甚至代码自我修改可能会有所帮助。 感谢您的回答,这似乎是一种非常有趣的方法。我有一个疑问,你确定int idx = one &amp; (two &lt;&lt; 1); 是正确的吗?我可能遗漏了一些东西,但我相信这将始终为 0(我的猜测是,当两者都为真时,它会导致 01&10)。它可以与or 甚至xor 一起使用。【参考方案2】:

有趣的问题。

double func(double a, double b, double c, double d, int i)
    if(i > 10 && a > b || i < 11 && a < b)
        return abs(a-b)/c;
    else
        return d/c;

首先想到的是:

“内联”限定符在哪里? 分支错误预测的可能性很大,并且 大量短路布尔评估。

我将假设 a 永远不会等于 b - 我的直觉是,您的数据集有 50% 的可能性是正确的,并且它允许进行一些有趣的优化。如果这不是真的,那么我没有任何迹象表明 Yakk 还没有。

double amb = a - b;
bool altb = a < b; // or signbit(amb) if it proves faster for you
double abs_amb = (1 - (altb << 1)) * amb;
bool use_amb = i > 10 != altb;
return (use_amb * abs_amb + !use_amb * d) / c;

我在构建工作时注意的一个目标是允许 CPU 执行管道中的一些并发性;可以这样说明:

amb    altb    i > 10
   \  /    \     /
  abs_amb  use_amb
        \  /      \
 use_amb*abs_amb  !use_amb*d
             \    /
              + /c

【讨论】:

+1 谢谢,这真的很有帮助!不过,我接受 Yakk 的回答,因为这个想法很相似,而且他是第一个。 很想知道这产生了多少改进。谢谢。 @c-urchin,我没有测试确切的情况,我的代码有点复杂,但是在使用这个想法之后,我的改进是 ~20%。【参考方案3】:

您是否尝试过像这样展开 if:

double func(double a, double b, double c, double d, int i)
    if(i > 10 && a > b)
        return (a-b)/c;
    if (i < 11 && a < b)
        return (b-a)/c;
    return d/c;

【讨论】:

我试过这个。在 VS2010 中,它似乎比 std::absfabs 慢 =/ 因为在我的情况下 a > b 比 a 你已经在做比较了,所以在第一种情况下,它甚至不需要做 abs 或 fabs。【参考方案4】:

我会查看调用 fabs() 生成的程序集。这可能是函数调用的开销。如果是这样,请将其替换为内联解决方案。如果检查绝对值的内容确实很昂贵,请尝试按位和 (&),位掩码除符号位外的所有位置均为 1。不过,我怀疑这会比标准库供应商的 fabs() 生成的便宜。

【讨论】:

以上是关于在 C++ 中加速双精度绝对值的主要内容,如果未能解决你的问题,请参考以下文章

获得两个双精度数组绝对差之和的有效方法

单精度和双精度有啥不同?

C++ (GCC) 中的四倍精度

double精确到几位小数

CC++ 中的绝对值函数:abs()cabs()fabs()labs()

PAT程序设计