【问题标题】:Is there a fast C or C++ standard library function for double precision inverse square root?是否有用于双精度反平方根的快速 C 或 C++ 标准库函数?
【发布时间】:2012-10-16 21:25:44
【问题描述】:

我发现自己在打字

double foo=1.0/sqrt(...);

很多,而且我听说现代处理器具有内置的逆平方根操作码。

是否有 C 或 C++ 标准库的反平方根函数

  1. 使用双精度浮点数?
  2. 1.0/sqrt(...)一样准确?
  3. 1.0/sqrt(...)的结果一样快或更快?

【问题讨论】:

  • @Pherric Oxide:那是平方反比,不是平方根反比。
  • #define INSQRT(x) (1.0/sqrt(x))
  • 您听说过的内置平方根逆指令是一个近似值,不如sqrt 精确。见tommesani.com/SSEReciprocal.html
  • 也许不如Quake III Arena 快。
  • @Mark Ransom:这基本上就是我要找的答案。

标签: c++ c double sqrt


【解决方案1】:

没有。不,没有。不在 C++ 中。没有。

【讨论】:

    【解决方案2】:

    您可以使用此函数进行更快的逆平方根计算
    维基百科上有一篇关于它如何工作的文章:https://en.wikipedia.org/wiki/Fast_inverse_square_root
    还有这个算法的 C 版本。

    float invSqrt( float number ){
        union {
            float f;
            uint32_t i;
        } conv;
    
        float x2;
        const float threehalfs = 1.5F;
    
        x2 = number * 0.5F;
        conv.f  = number;
        conv.i  = 0x5f3759df - ( conv.i >> 1 );
        conv.f  = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) );
        return conv.f;
    }
    

    【讨论】:

    • 阅读这篇文章给了我这个问题的想法。一篇关于快速反平方根的文章说,某些硬件具有反平方根指令,因为它在图形代码中出现了多少。我当时不能使用这个算法,因为我需要完全的双精度,但是对任何读过这个答案但没有听说过的人投赞成票:)。
    【解决方案3】:

    我不知道这方面的标准化 C API,但这并不意味着您不能使用快速逆 sqrt 指令,只要您愿意编写平台相关的内在函数

    我们以 64 位 x86 和 AVX 为例,您可以在其中使用 _mm256_rsqrt_ps() 近似平方根的倒数。或者更具体地说:使用 SIMD 一次完成 8 个平方根。

    #include <immintrin.h>
    
    ...
    
    float inputs[8] = { ... } __attribute__ ((aligned (32)));
    __m256 input = _mm256_load_ps(inputs);
    __m256 invroot = _mm256_rsqrt_ps(input);
    
    

    同样,您可以在 ARM 上将内部 vrsqrteq_f32 与 NEON 一起使用。在这种情况下,SIMD 是 4 宽的,因此它会一次计算四个平方根。

    #include <arm_neon.h>
    
    ...
    
    float32x4_t sqrt_reciprocal = vrsqrteq_f32(x);
    

    即使每批只需要一个根值,它仍然比一个完整的平方根更快。只需在 SIMD 寄存器的全部或一个通道中设置输入。这样,您就不必通过加载操作来遍历您的内存。在 x86 上通过 _mm256_set1_ps(x) 完成。

    【讨论】:

      【解决方案4】:

      违反了约束 1. 和 2.(这也不是标准的),但它仍然可以帮助某人浏览...

      我使用ASMJIT 来即时编译您要查找的确切汇编操作:RSQRTSS(单精度,可以,但应该与双精度类似)。

      我的代码是这样的(参见我在另一篇文章中的answer):

         typedef float(*JITFunc)();
      
         JITFunc func;
         asmjit::JitRuntime jit_runtime;
         asmjit::CodeHolder code;
         code.init(jit_runtime.getCodeInfo());
      
         asmjit::X86Compiler cc(&code);
         cc.addFunc(asmjit::FuncSignature0<float>());
      
         float value = 2.71; // Some example value.
         asmjit::X86Xmm x = cc.newXmm();
         uint32_t *i = reinterpret_cast<uint32_t*>(&value);
         cc.mov(asmjit::x86::eax, i[0]);
         cc.movd(x, asmjit::x86::eax);
      
         cc.rsqrtss(x, x);   // THE asm function.
      
         cc.ret(x);
      
         cc.endFunc();
         cc.finalize();
      
         jit_runtime.add(&func, &code);
      
         // Now, func() can be used as the result to rsqrt(value).
      

      如果您只执行一次 JIT 编译部分,稍后使用不同的值调用它,这应该比 1.0/sqrt(...) 更快(虽然准确度略低,但这是您正在谈论的内置操作所固有的) .

      【讨论】:

        【解决方案5】:

        如果您不害怕使用自己的函数,请尝试以下操作:

        template <typename T>
        T invsqrt(T x)
        {
            return 1.0 / std::sqrt(x);
        }
        

        它应该与任何现代优化编译器中的原始1.0 / std::sqrt(x) 一样快。此外,它可以与双精度或浮点数一起使用。

        【讨论】:

        • 违反了问题中的规则#3!
        • 对不起,据我所知,它应该“一样快”。
        • 阅读stackoverflow.com/questions/2442358/… 了解为什么模板函数应该比非模板代码慢。
        • 另外,如果您在 gcc 中启用-ffast-math,它将使用近似值来求平方根的倒数。这将确保它与常规平方根一样快/更快。
        【解决方案6】:

        如果你发现自己一遍又一遍地写同样的东西,你应该想一想“函数!”:

        double invsqrt(const double x)
        {
            return 1.0 / std::sqrt(x);
        }
        

        现在代码更加自我记录:人们不必推断 1.0 / std::sqrt(x) 是平方根的倒数,他们阅读它。此外,您现在可以插入所需的任何实现,并且每个调用站点都会自动使用更新后的定义。

        要回答你的问题,不,它没有 C(++) 函数,但现在你已经做了一个,如果你发现你的表现太缺乏,你可以替换你自己的定义。

        【讨论】:

        • @PrototypeStark:因为它不像非此即彼那样简单。一个是类型检查的、可调试的、可作用域的、可重载的,它的参数评估一次是一个表达式,等等(函数的所有特性),另一个不是。这是一个单一的反对票,这不是世界末日;我知道没有从对方那里得到理由是令人沮丧的,但事实就是如此。
        • 我认为1.0/sqrt(x)invsqrt(x) 更容易理解为平方根的倒数,前者使用不那么模糊的数学符号而不是缩写。
        • "inverse square root" 只是表示平方根的倒数。这不是细节,而是函数名的字面意思。
        • @Dan:您将实现与规范混为一谈。你是对的,平方根的倒数是平方根的倒数,但是如何从那个得到1.0 / sqrt(x)?当然,这并不难,但这不是重点:它仍然是从规范到实现的划分。隐藏实现,保留规范;它使推理和维护您的程序更容易。考虑一下在整个程序中优化每一个逆平方根计算是多么容易,只需更改实现并保持规范。
        • @GManNickG:虽然我通常是第一个同意这种逻辑的人,但还是有限制的。你不会写函数multiplyByTwo——你会写*2。就我个人而言,我会说平方根倒数的例子就在边界上。
        【解决方案7】:

        为什么不试试这个? #define INSQRT(x) (1.0/sqrt(x))

        它一样快,需要更少的输入(让你觉得它是一个函数),使用双精度,精确到 1/sqrt(..)

        【讨论】:

        • 我没有投反对票,但是当函数可以使用时,这里的宏没有用处。 (你甚至自己说过:让它感觉像一个函数?实际上就是做一个函数。)
        • @GManNickG 我没有将其转换为函数的原因是,因为问题清楚地提到:“与 1.0/sqrt(...) 的结果一样快或更快”。使其成为一个函数会增加额外的开销,使“语句” 1.0/sqrt(...) SLOWER.
        • 过去十年的任何编译器都没有。
        • @PrototypeStark:请提供基准来支持您声称使用真实函数会更慢的说法。在没有证据表明它们需要满足某些标准的情况下,可以安全地避免使用宏。也就是说,我总是随身携带我的#define isNaN(x) ((x)!=(x));有时候,这么糟糕只是感觉很好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-25
        • 1970-01-01
        • 2011-12-04
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多