【问题标题】:Fast floating point abs function快速浮点 abs 函数
【发布时间】:2019-11-28 23:59:53
【问题描述】:

在 C99 的 x86-64 架构上获取标准 32 位浮点数的绝对值的最快方法是什么?内置函数 fabsffabs 不够快。我目前的做法有点绕:

unsigned int tmp = *((unsigned int *)&f) & 0x7fffffff;
float abs = *((float *)&tmp);

它可以工作,但很难看。而且我不确定它是最佳的吗?

不要再告诉我关于类型双关的指针,因为这不是我要问的。我知道代码可以使用联合来表达,但这并不重要,因为在所有编译器(过去 10 年编写的)上,它都会发出完全相同的代码。

【问题讨论】:

  • 您不认为,如果有更快的方法可用,图书馆提供商会选择它吗?每秒有多少次你需要这样做而快得令人眼花缭乱的库例程无法管理?您可能还需要考虑您的方法可能不适用于“不常见”值的可能性,例如NaN。我必须进行调查才能确定,但​​这可能是您需要考虑的事情。
  • 你所做的确实打破了严格的别名,你应该使用无符号的位旋转,当然还有一个联合
  • 类型双关语floatunsigned 违反C11 Standard - §6.5 Expressions (p6,7)“严格的别名规则”。
  • GCCClang 都将 fabsf 优化为 andps 指令。你说它不够快的依据是什么?大型优化可以通过对代码的算法改进来完成,但微优化很大程度上依赖于上下文。您应该确定您正在使用的特定编译器版本和开关,并显示带有上下文的源代码——最好是一个可以计时的完整例程,在代码中显示fabsf 是瓶颈以及原因(这对于fabsf 来说是非常不寻常的)成为一个软件的瓶颈)。
  • 如果速度不够快,那么我相信你只能通过 SIMD 一次获取多个值的绝对值来加快速度

标签: c performance floating-point c99


【解决方案1】:

更少的标准违规:

/* use type punning instead of pointer arithmatics, to require proper alignment */
static inline float float2absf(float f) {
  /* optimizer will optimize away the `if` statement and the library call */
  if (sizeof(float) == sizeof(uint32_t)) {
    union {
      float f;
      uint32_t i;
    } u;
    u.f = f;
    u.i &= 0x7fffffff;
    return u.f;
  }
  return fabsf(f);
}

恕我直言,使用库函数会更安全。这将提高代码的可移植性,尤其是在您可能遇到非 IEEE 浮点表示或类型大小可能不同的平台上。

一般来说,一旦为您的平台编译,库函数应该提供最快的解决方案

话虽如此,库调用需要堆栈管理和代码跳转,除非优化掉,对于简单的位更改函数,这可能会导致操作次数增加一倍以上,并且缓存未命中。在许多情况下,这可以通过使用编译器内置函数来避免,这可以由编译器自动完成(它可以将库函数优化为内联指令)。

您的位方法(理论上)是正确的,并且可以优化与函数调用相关的操作,并改善代码局部性……尽管使用编译器内置函数和优化也可以实现相同的目标。

另外,请注意,您的方法不符合标准,它假定 sizeof(int) == sizeof(float)... 我认为使用联合的类型双关语会有所改善。

此外,使用内联函数可以像使用宏一样工作,并使代码更具可读性。此外,如果类型大小不匹配,它可能允许回退到库函数。

【讨论】:

  • 库调用不需要“堆栈管理”或“代码跳转”。允许编译器进行优化,Apple LLVM 10.0.1 with clang-1001.0.46.4 for x86_64 将fabsf 优化为andps,没有函数调用。
  • @EricPostpischil ,这在大多数但不是所有情况下可能都是正确的。在编写驱动程序和内核模块时,由于可能的副作用以及函数名称未映射到库函数(而是映射到其他一些函数)的可能性,编译器不能/不优化库函数)。正如我所提到的,恕我直言,应该提供使用库函数或内置编译器。
  • 内核例程log是一个麻烦,因为它与标准库例程重叠,但这个单独的名字可以用-fno-builtin-log管理,所以没有必要禁用所有的编译器优化数学库例程,这对fabsf 来说不是问题。库调用不需要汇编语言中的实际函数调用。
  • @EricPostpischil - 如前所述,我通常同意你的看法。我只是不认为库函数应该被视为神圣的,我相信如果 OP 想要推出自己的功能是可以的。我自己的经验表明,某些库函数比滚动您自己的函数要慢,尤其是在受到局部性和其他可移植性问题的阻碍时(例如 randatol,其中 my own versions, fio_rand64 and fio_atol 的性能优于库的代码)。
  • @EricPostpischil - P.S. - 我更新了答案以反映讨论。
猜你喜欢
  • 1970-01-01
  • 2010-10-23
  • 2016-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多