std :: fabs()的优化不好?

时间:2014-05-06 09:03:49

标签: c++ bit-manipulation

最近我正在使用代码类似于:

的应用程序
for (auto x = 0; x < width - 1 - left; ++x)
{
    // store / reset points
    temp = hPoint = 0;
    for(int channel = 0; channel < audioData.size(); channel++)
    {
        if (peakmode) /* fir rms of window size */
        {
            for (int z = 0; z < sizeFactor; z++)
            {
                temp += audioData[channel][x * sizeFactor + z + offset];
            }
            hPoint += temp / sizeFactor;
        }
        else /* highest sample in window */
        {
            for (int z = 0; z < sizeFactor; z++)
            {
                temp = audioData[channel][x * sizeFactor + z + offset];
                if (std::fabs(temp) > std::fabs(hPoint))
                hPoint = temp;
            }
        }
        .. some other code
    }
    ... some more code
}

这是在一个图形渲染循环中,称为50-100次/秒,多个通道中的缓冲区高达192kHz。因此,很多数据都在最里面的循环中运行,并且分析显示这是一个热点。

在我看来,可以将浮点数转换为整数并删除符号位,然后仅使用临时值将其转换回来。它看起来像这样:

if ((const float &&)(*((int *)&temp) & ~0x80000000) > (const float &&)(*((int *)&hPoint) & ~0x80000000))
    hPoint = temp;

这使渲染时间减少了12倍,同时仍然产生相同的有效输出。请注意,audiodata中的所有内容都要事先清理,以便不包含nans / infs / denormals,并且只有[-1,1]的范围。

是否存在此优化会产生错误结果的极端情况 - 或者,为什么标准库函数没有像这样实现?我认为它与处理非正常值有关吗?

e:浮点模型的布局符合ieee,sizeof(float)== sizeof(int)== 4

6 个答案:

答案 0 :(得分:4)

好吧,您将浮点模式设置为符合IEEE标准。通常,对于像--fast-math这样的开关,编译器可以忽略像NaN,INF和非正规的IEEE极端情况。如果编译器也使用内在函数,它可能会发出相同的代码。

BTW,如果你将采用IEEE格式,那么在比较之前不需要将反转回浮动。 IEEE格式很漂亮:对于所有正有限值,a<b当且仅当reinterpret_cast<int_type>(a) < reinterpret_cast<int_type>(b)

答案 1 :(得分:4)

  

在我看来,可以将浮点数转换为整数并删除符号位,然后仅使用临时值将其转换回来。

不,你不能,因为这违反了strict aliasing rule

  

是否存在此优化会产生错误结果的极端情况

从技术上讲,此代码会导致未定义的行为,因此始终会产生错误的&#34;结果&#34;。从某种意义上说,绝对值的结果总是意外或不正确,但从某种意义上说,如果程序有未定义的行为,你就无法推断程序的作用。

  

或者,为什么标准库函数没有像这样实现?

你的怀疑是合理的,处理非正规值和其他异常值是棘手的,stdlib函数也需要考虑这些,而另一个原因仍然是未定义的行为。

如果您关心效果,则采用一种(非)解决方案:

您可以使用联合而不是强制转换和指针。 不幸的是,这只适用于C,而不是C ++。这不会导致UB,但它仍然不可移植(尽管它可能适用于大多数,如果并非所有平台都使用IEEE-754。

union {
    float f;
    unsigned u;
} pun = { .f = -3.14 };

pun.u &= ~0x80000000;

printf("abs(-pi) = %f\n", pun.f);

但是,如果被授予,这可能或者可能不会比调用fabs()更快。只有一件事是肯定的:它永远不会是正确的。

答案 2 :(得分:2)

您希望fabs()在硬件中实现。毕竟,1980年有8087指令。你不会打败硬件。

答案 3 :(得分:1)

标准库函数如何实现它是......依赖于实现。因此,您可能会发现具有不同性能的标准库的不同实现。

我想你可能在int不是32位的平台上遇到问题。您最好使用int32_t(cstdint&gt;)

据我所知,std :: abs之前是内联的吗?或者您观察到的优化主要是由于函数调用的抑制?

答案 4 :(得分:1)

关于重构如何改善绩效的一些观察:

  • 如上所述,x * sizeFactor + offset可以从内循环中分解出来

  • peakmode实际上是一个改变函数行为的开关 - 制作两个函数而不是在循环中测试开关。这有两个好处:

    1. 更易于维护
    2. 更少的局部变量和代码路径可以阻止优化。

  • tempsizeFactor之前的划分可以推迟到channel版本的peakmode循环之外。

  • 每当abs(hPoint)更新时,都可以预先计算
  • hPoint

  • 如果audioData是向量的向量,则可以通过在audioData[channel]循环体的开头引用channel来获得一些性能优势,从而减少数组索引在z循环中向下到一维。

  • 最后,对您认为合适的fabs的计算应用任何特定的优化。你在这里做的任何事都会损害便携性,所以这是最后的手段。

答案 5 :(得分:0)

在VS2008中,使用以下内容跟踪hpointhIsNeg的绝对值以记住它是正面还是负面,大约是使用fabs()的两倍:

int hIsNeg=0 ;
...
//Inside loop, replacing
//    if (std::fabs(temp) > std::fabs(hPoint))
//        hPoint = temp;
if( temp < 0 )
{
    if( -temp > hpoint )
    {
        hpoint = -temp ;
        hIsNeg = 1 ;
    }
}
else
{
    if( temp > hpoint )
    {
        hpoint = temp ;
        hIsNeg = 0 ;
    }
}
...
//After loop
if( hIsNeg )
    hpoint = -hpoint ;