c++ - 为什么将 0.1f 更改为 0 会使性能降低 10 倍?

标签 c++ performance visual-studio-2010 compilation floating-point

为什么会有这段代码,

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0.1f; // <--
        y[i] = y[i] - 0.1f; // <--
    }
}

运行速度比后面的位快 10 倍以上(除特别说明外相同)?

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0; // <--
        y[i] = y[i] - 0; // <--
    }
}

使用 Visual Studio 2010 SP1 编译时。 优化级别是 -02 并启用了 sse2。 我没有用其他编译器测试过。

最佳答案

欢迎来到 denormalized floating-point 的世界! 它们会严重破坏性能!!!

非正规(或次正规)数字是一种从浮点表示中获取一些非常接近零的额外值的技巧。非规范化浮点运算可能比规范化浮点运算慢几十到几百倍。这是因为许多处理器不能直接处理它们,必须使用微码捕获和解析它们。

如果您在 10,000 次迭代后打印出数字,您会看到它们已经收敛到不同的值,具体取决于使用的是 0 还是 0.1

这是在 x64 上编译的测试代码:

int main() {

    double start = omp_get_wtime();

    const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
    const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
    float y[16];
    for(int i=0;i<16;i++)
    {
        y[i]=x[i];
    }
    for(int j=0;j<9000000;j++)
    {
        for(int i=0;i<16;i++)
        {
            y[i]*=x[i];
            y[i]/=z[i];
#ifdef FLOATING
            y[i]=y[i]+0.1f;
            y[i]=y[i]-0.1f;
#else
            y[i]=y[i]+0;
            y[i]=y[i]-0;
#endif

            if (j > 10000)
                cout << y[i] << "  ";
        }
        if (j > 10000)
            cout << endl;
    }

    double end = omp_get_wtime();
    cout << end - start << endl;

    system("pause");
    return 0;
}

输出:

#define FLOATING
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007

//#define FLOATING
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.46842e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.45208e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044

请注意,在第二次运行中,数字非常接近于零。

非规范化数字通常很少见,因此大多数处理器不会尝试有效地处理它们。


为了证明这与非规范化数字有关,如果我们通过将其添加到代码开头来将非规范化清零:

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

然后带有 0 的版本不再慢 10 倍,实际上变得更快。 (这需要在启用 SSE 的情况下编译代码。)

这意味着我们不是使用这些奇怪的低精度几乎为零的值,而是四舍五入到零。

时序:Core i7 920 @ 3.5 GHz:

//  Don't flush denormals to zero.
0.1f: 0.564067
0   : 26.7669

//  Flush denormals to zero.
0.1f: 0.587117
0   : 0.341406

最后,这真的与它是整数还是浮点无关。 00.1f 被转换/存储到两个循环之外的寄存器中。所以这对性能没有影响。

关于c++ - 为什么将 0.1f 更改为 0 会使性能降低 10 倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9314534/

相关文章:

c++ - 你开始使用 C++0x 了吗?

c++ - 存储各种尺寸也各不相同的结构的好方法?

c++ - 为什么我在以受限用户身份运行时会在 DLLMain 中获得 GPF?

c++ time() 函数在 solaris 中的性能

c++ - 我怎样才能使这个简单的 Fortran 90 代码更快?

c++ - 如何将深度图像复制到彩色图像?

mysql - 与其他实体有随机关系的实体

visual-studio-2010 - 如何设置VS201 0 auto 换行的行长

c++ - SFINAE 不适用于 Visual Studio 2010 for std::is_pointer

visual-studio - NUnit 中的跟踪仅在从 Visual Studio 进行调试时有效