【问题标题】:Is it still worth trying to create optimizations for sqrt() in C?是否仍然值得尝试在 C 中为 sqrt() 创建优化?
【发布时间】:2018-01-06 23:28:25
【问题描述】:

用于创建更快的 sqrt() 实现的旧技巧(查找表、近似函数)是否仍然有用,或者默认实现是否与现代编译器和硬件一样快?

【问题讨论】:

标签: c performance optimization


【解决方案1】:

规则 1:优化前的配置文件

在投入任何努力相信您可以击败优化器之前,您必须分析所有内容并发现瓶颈的真正所在。一般来说,sqrt() 本身不太可能是您的瓶颈。

规则 2:在替换标准函数之前替换算法

即使sqrt() 是瓶颈,那么仍有合理的可能存在可以消除调用的算法方法(例如按长度平方排序距离,无需调用任何数学函数即可轻松计算) sqrt() 首先。

如果你什么都不做,编译器会为你做什么

许多现代 C 编译器都愿意在更高的优化级别内联 CRT 函数,使自然表达式(包括对 sqrt() 的调用)尽可能快地进行。

特别是,我检查了 MinGW gcc v3.4.5,它将对 sqrt() 的调用替换为对 FPU 状态进行洗牌的内联代码,并在核心使用了 FSQRT 指令。由于 C 标准与 IEEE 754 浮点交互的方式,它确实必须遵循 FSQRT 一些代码来检查异常情况并从运行时库调用真正的 sqrt() 函数,以便浮点异常可由库按标准要求处理。

使用 sqrt() 内联并在更大的 all double 表达式的上下文中使用,在考虑到标准合规性和保持全精度的约束条件下,结果尽可能高效。

对于这种(非常常见的)编译器和目标平台的组合,在不了解用例的情况下,这个结果还不错,代码清晰可维护。

在实践中,任何技巧都会使代码变得不那么清晰,并且可能更难维护。毕竟,您更愿意维护(-b + sqrt(b*b - 4.*a*c)) / (2*a) 还是内联汇编和表格的不透明块?

此外,在实践中,您通常可以依靠编译器和库作者来充分利用您平台的功能,并且通常比您更了解优化的细微之处。

但是,在极少数情况下,可以做得更好。

这样的情况之一是在计算中,您知道自己真正需要多少精度,并且知道您不依赖于 C 标准的浮点异常处理,而是可以与硬件平台提供的东西相处。

编辑:我重新排列了文本,以强调 Jonathan Leffler 在 cmets 中建议的分析和算法。谢谢,乔纳森。

Edit2:修正了 kmm 敏锐的眼睛发现的二次示例中的优先错字。

【讨论】:

  • 我很想把最后两段放在前面 - 否则,一个很好的答案。
  • @Jonathan,这是个好主意。我把东西洗了一点,谢谢。
  • +1,说得好。我要补充一点,作为何时优化这类事情的一个例子,在 3D 图形程序(特定游戏)中执行矢量规范化。在这种情况下,您会非常频繁地计算许多反平方根,并且您不需要最大精度,因此使用更快(但不太准确)的 sqrt 实现是非常值得的。
  • +1。很清楚,谢谢。您在计算判别式时忘记了 2*a 附近的括号。
【解决方案2】:

Sqrt 在大多数系统上基本不变。这是一个相对较慢的操作,但总体系统速度有所提高,因此可能不值得尝试使用“技巧”。

使用近似值来优化它可以实现的(次要)收益的决定实际上取决于您。现代硬件已经消除了对这些类型牺牲(速度与精度)的一些需求,但在某些情况下,这仍然很有价值。

我会使用分析来确定这是否“仍然有用”。

【讨论】:

    【解决方案3】:

    如果您已经证明代码中对 sqrt() 的调用是分析器的瓶颈,那么可能值得尝试创建一个优化版本。否则就是浪费时间。

    【讨论】:

    • 即便如此,看看为什么 sqrt() 是瓶颈并修改算法可能与修改 sqrt() 函数一样有效。
    【解决方案4】:

    这可能是计算平方根最快的方法:

    float fastsqrt(float val)  {
            union
            {
                    int tmp;
                    float val;
            } u;
            u.val = val;
            u.tmp -= 1<<23; /* Remove last bit so 1.0 gives 1.0 */
            /* tmp is now an approximation to logbase2(val) */
            u.tmp >>= 1; /* divide by 2 */
            u.tmp += 1<<29; /* add 64 to exponent: (e+127)/2 =(e/2)+63, */
            /* that represents (e/2)-64 but we want e/2 */
            return u.val;
    }
    

    wikipedia article


    这可能是计算平方根倒数的最快方法。假设最多 0.00175228 错误。

    float InvSqrt (float x)
    {
        float xhalf = 0.5f*x;
        int i = *(int*)&x;
        i = 0x5f3759df - (i>>1);
        x = *(float*)&i;
        return x*(1.5f - xhalf*x*x);
    }
    

    这(非常粗略)比(float)(1.0/sqrt(x))快4倍

    wikipedia article

    【讨论】:

      【解决方案5】:

      通常可以安全地假设标准库开发人员非常聪明,并且编写了高性能代码。一般来说,您不太可能匹配它们。

      所以问题就变成了,你知道什么能让你做得更好吗?我不是在询问用于计算平方根的特殊算法(标准库开发人员也知道这些,如果它们通常值得,他们已经使用它们了),但是你有关于 您的用例会改变情况吗?

      您只需要有限的精度吗?如果是这样,与标准库版本相比,您可以加快速度,这必须是准确的。

      或者您是否知道您的应用程序将始终在特定类型的 CPU 上运行?然后你可以看看那个 CPU 的 sqrt 指令效率如何,看看有没有更好的替代方案。当然,这样做的缺点是,如果我在另一个 CPU 上运行您的应用程序,您的代码可能会比标准 sqrt() 慢。

      您能否在您的代码中做出标准库开发人员无法做到的假设?

      对于“实现标准库 sqrt 的有效替代”问题,您不太可能想出更好的解决方案。

      但您或许可以想出一个解决方案来解决“针对这种特定情况实现有效的平方根函数”的问题。

      【讨论】:

        【解决方案6】:

        为什么不呢?你可能会学到很多东西!

        【讨论】:

          【解决方案7】:

          由于现代计算机的设计方式,我很难相信 sqrt 函数是您的应用程序的瓶颈。假设这不是关于某些疯狂的低端处理器的问题,那么您访问 CPU 缓存之外的内存的速度会受到极大的影响,因此除非您的算法正在对很少的数字进行数学运算(足够他们全部基本上适合 L1 和 L2 缓存)你不会注意到优化任何算法的速度。

          【讨论】:

            【解决方案8】:

            即使现在我仍然觉得它很有用,尽管这是为了响应变形网格而对每帧一百万多个向量进行归一化的上下文。

            也就是说,我通常不会创建自己的优化,而是依赖作为 SIMD 指令提供的逆平方根的粗略近似值:rsqrtps。如果您愿意为速度牺牲精度,那么这对于加速某些实际案例仍然非常有用。使用rsqrtps 实际上可以将包括顶点法线变形和归一化在内的整个操作减少到几乎一半的时间,但代价是结果的精度(也就是说,以人眼几乎无法注意到的方式) )。

            我还发现,经常错误地归功于 John Carmack 的快速逆 sqrt 仍然可以提高标量情况下的性能,尽管我现在不怎么使用它。如果您愿意牺牲准确性,通常会自然而然地获得一些速度提升。话虽如此,如果您不想为了速度而牺牲精度,我什至不会尝试击败 C 的 sqrt

            如果您想击败标准实现,通常必须牺牲解决方案的通用性(例如其精度),无论它是数学函数还是 malloc,这往往适用。我可以轻松地击败malloc,使用一个适用范围狭窄的免费列表,该列表缺乏适用于非常特定上下文的线程安全性。使用通用分配器击败它是另一回事,该分配器可以分配可变大小的内存块并在任何给定时间释放其中任何一个。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-12-03
              • 2011-12-11
              • 1970-01-01
              • 2015-11-20
              • 2023-04-09
              • 2019-06-26
              • 2012-10-16
              相关资源
              最近更新 更多