为什么这段代码

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 进行编译。(我还没有测试使用其他编译器。)

2012-02-16 15:58:39
问题评论:

0是一个整数,因此很可能它已转换为运行时浮点型。

您是如何衡量的差异的?和哪些选项是否使用了您在编译时?

为什么不编译器只放 + /-0 在此种情况下呢? !?

@Zyx2000 编译器不是任何地方不久,愚蠢的。拆解 LINQPad 中一个微不足道的例子显示,它 spits 出相同的代码是否使用00f0d或甚至(int)0在上下文中,需要double

优化级别是什么?

回答:

欢迎世界不正常的浮点他们可以肆意破坏性能!!!

非正规 (或 subnormal) 号是种黑客获取一些额外值非常接近于零出浮动点表示。操作不正常浮可以比数十甚至数百个时间要慢上规范化浮点。这是因为很多处理器不能直接对其进行处理和必须捕获并解决这些问题使用的微码。

如果 10000 次迭代后打印出数字,您将看到它们具有收敛为不同的值,具体取决于是否使用00.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

请注意如何在第二个运行中的数字是非常接近于零。

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


若要证明它具有与非规范化数,是否我们通过添加到代码的开始刷新 denormals 为零

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

0版本不再慢 10 倍然后实际上变得更快。(这需要使用 SSE 启用编译的代码。

这意味着,而不是使用这些奇怪的低精度近似为零值,我们只是舍入到零相反。

排练时间︰ 酷睿 i7 920 @ 3.5 g h z:

//  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是转换/存储到寄存器之外两个环。因此,对性能没有影响。

我仍然发现它有些怪,"+ 0"没有完全出通过优化编译器默认。就这样发生了如果他放"+ 0.0 f"?

@s73v3r 是一个很好的问题。现在,我看该程序集,即使+ 0.0f获取优化出。如果我不得不猜测,这可能是+ 0.0f会产生负面影响,如果y[i]恰巧是信令的NaN或东西...我可能是错误的不幸。

双精度型值仍会遇到同样的问题在许多情况下,只是在一个不同的数字数值。刷新到零是音频应用程序的正常 (和其他人对于可以承受丢失各处 1e-38),但我认为不能应用于 x87。如果 FTZ,没有音频应用程序的常规修复是注入极低振幅 (不听见) 直流或方形波信号抖动远离号 denormality。

@Isaac 由于 y [i] 时明显小于 0.1 添加数中的最高有效数字变得更高,因为导致精度损失。

@s73v3r︰ 由于浮有负 0,添加 +0.f 的结果,不能出优化 +0.f-.0f 是 + 0.f。因此添加 0.f 不是一个标识操作并不能优化出。

使用gcc并将比较应用于生成的程序集生成仅此差异︰

73c68,69
<   movss   LCPI1_0(%rip), %xmm1
---
>   movabsq $0, %rcx
>   cvtsi2ssq   %rcx, %xmm1
81d76
<   subss   %xmm1, %xmm0

一种是 10 倍速度确实的cvtsi2ssq

显然, XMM寄存器的float版本使用从内存加载,而int版本将真实int值 0 转换为float使用cvtsi2ssq指令,花大量的时间。通过-O3为 gcc 不起作用。(gcc 版本 4.2.1)。

(而不float使用double并不重要,只不过变成cvtsi2sdq cvtsi2ssq.)

更新

一些额外的测试表明,它不一定是cvtsi2ssq指令。一次消除 (使用int ai=0;float a=ai;a使用而不0),保持速度差异。@Mysticial 是合适的因此不正常的浮点型值进行区别。这可以通过测试值介于00.1f上面的代码中的车削点时,大约有0.00000000000000000000000000000001,循环而突然 10 倍。

更新 << 1

这种有趣的现象小 visualisation:

  • 第 1 列︰ 浮点数,在每个迭代的一半
  • 第二列︰ 此浮动二进制表示
  • 第 3 列︰ 求和此浮点 1e7 次所用时间

您可以清楚地看到指数 (最后的 9 位) 更改为其最小值,非规范化油然而生。此时,简单的加法变成慢 20 倍。

0.000000000000000000000000000000000100000004670110: 10111100001101110010000011100000 45 ms
0.000000000000000000000000000000000050000002335055: 10111100001101110010000101100000 43 ms
0.000000000000000000000000000000000025000001167528: 10111100001101110010000001100000 43 ms
0.000000000000000000000000000000000012500000583764: 10111100001101110010000110100000 42 ms
0.000000000000000000000000000000000006250000291882: 10111100001101110010000010100000 48 ms
0.000000000000000000000000000000000003125000145941: 10111100001101110010000100100000 43 ms
0.000000000000000000000000000000000001562500072970: 10111100001101110010000000100000 42 ms
0.000000000000000000000000000000000000781250036485: 10111100001101110010000111000000 42 ms
0.000000000000000000000000000000000000390625018243: 10111100001101110010000011000000 42 ms
0.000000000000000000000000000000000000195312509121: 10111100001101110010000101000000 43 ms
0.000000000000000000000000000000000000097656254561: 10111100001101110010000001000000 42 ms
0.000000000000000000000000000000000000048828127280: 10111100001101110010000110000000 44 ms
0.000000000000000000000000000000000000024414063640: 10111100001101110010000010000000 42 ms
0.000000000000000000000000000000000000012207031820: 10111100001101110010000100000000 42 ms
0.000000000000000000000000000000000000006103515209: 01111000011011100100001000000000 789 ms
0.000000000000000000000000000000000000003051757605: 11110000110111001000010000000000 788 ms
0.000000000000000000000000000000000000001525879503: 00010001101110010000100000000000 788 ms
0.000000000000000000000000000000000000000762939751: 00100011011100100001000000000000 795 ms
0.000000000000000000000000000000000000000381469876: 01000110111001000010000000000000 896 ms
0.000000000000000000000000000000000000000190734938: 10001101110010000100000000000000 813 ms
0.000000000000000000000000000000000000000095366768: 00011011100100001000000000000000 798 ms
0.000000000000000000000000000000000000000047683384: 00110111001000010000000000000000 791 ms
0.000000000000000000000000000000000000000023841692: 01101110010000100000000000000000 802 ms
0.000000000000000000000000000000000000000011920846: 11011100100001000000000000000000 809 ms
0.000000000000000000000000000000000000000005961124: 01111001000010000000000000000000 795 ms
0.000000000000000000000000000000000000000002980562: 11110010000100000000000000000000 835 ms
0.000000000000000000000000000000000000000001490982: 00010100001000000000000000000000 864 ms
0.000000000000000000000000000000000000000000745491: 00101000010000000000000000000000 915 ms
0.000000000000000000000000000000000000000000372745: 01010000100000000000000000000000 918 ms
0.000000000000000000000000000000000000000000186373: 10100001000000000000000000000000 881 ms
0.000000000000000000000000000000000000000000092486: 01000010000000000000000000000000 857 ms
0.000000000000000000000000000000000000000000046243: 10000100000000000000000000000000 861 ms
0.000000000000000000000000000000000000000000022421: 00001000000000000000000000000000 855 ms
0.000000000000000000000000000000000000000000011210: 00010000000000000000000000000000 887 ms
0.000000000000000000000000000000000000000000005605: 00100000000000000000000000000000 799 ms
0.000000000000000000000000000000000000000000002803: 01000000000000000000000000000000 828 ms
0.000000000000000000000000000000000000000000001401: 10000000000000000000000000000000 815 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms
0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 44 ms

堆栈溢出的问题中找不到关于 ARM 等讨论在 OBJECTIVE-C 中的浮点非正常化? .

+ 1 的工作量,并显示该实际测试已表明的触发条件

这是很酷的 ascii 字符图刀片式服务器的提示。比喻吗?

云中冲突双刃剑立即进入脑海 !

-Os 不要修复它,但是-ffast-math(我使用的所有时间,IMO 的极端情况下,它会导致精度问题不应该调高正确设计程序中仍。)

希望我能再次为该相当几乎-ascii 字符-图的可视化 + 1。我猜到它的人永远不会看浮动前的点表示的二进制工作奇迹 !

在 gcc 可以与此启用 FTZ 和 DAZ:

#include <xmmintrin.h>

#define FTZ 1
#define DAZ 1   

void enableFtzDaz()
{
    int mxcsr = _mm_getcsr ();

    if (FTZ) {
            mxcsr |= (1<<15) | (1<<11);
    }

    if (DAZ) {
            mxcsr |= (1<<6);
    }

    _mm_setcsr (mxcsr);
}

此外使用 gcc 开关:-msse mfpmath = sse

(Carl Hetherington [1] 的相应贷方)

[1] http://carlh.net/plugins/denormals.php

另一个、 可移植性的舍入 (linux.die.net/man/3/fesetround) (但这会影响所有的浮点操作,而不仅仅是 subnormals方法请参见fesetround()fenv.h (为 C99 定义))

您确实需要 1 << 15 和 1 << 11 FTZ 吗?我只看过 1 << 15 引用的其他地方...

@fig: 1 << 下溢蒙是 11。更多的信息︰ softpixel.com/~cwright/programming/simd/sse.php

@GermanGarcia 这不应答 OPs 的问题;问题是"为什么这段代码,运行 10 次比..."-应该尝试回答此问题提供此种解决方法之前或在注释中提供此。

它已经由于非正常化浮点使用。如何除去它并对性能产生影响的?有 scoured 互联网有关的终止方法非正规号,似乎没有"最佳"方法来尚未执行此操作。我在不同的环境中发现了可能适合这三种方法︰

  • 可能无法在某些 GCC 环境中工作︰

    // Requires #include <fenv.h>
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
    
  • 不能在一些 Visual Studio 环境︰ 1

    // Requires #include <xmmintrin.h>
    _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) );
    // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both.
    // You might also want to use the underflow mask (1<<11)
    
  • 似乎在 GCC 和 Visual Studio 中工作︰

    // Requires #include <xmmintrin.h>
    // Requires #include <pmmintrin.h>
    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
    
  • 英特尔编译器有个选项可以禁用默认情况下,现代的英特尔 Cpu 上的 denormals。这里更多的详细信息

  • 编译器开关。-ffast-math-msse-mfpmath=sse将禁用 denormals 和速度更快,做一些其他事情,但遗憾的是还执行许多其他代码可能会破坏代码的近似值。仔细测试 !Visual Studio 编译器的快速数学相当于/fp:fast ,但我尚未能确认这是否还禁用 denormals。1

这听起来像一个相当不错的答案不同但相关的问题 (如何防止数值计算中产生不正常的结果?)它不但是回答这一问题。

IFTFY @BenVoigt

.Exe,而 Windows 32 位和 linux 不能启动时,Windows X64 通过突然下溢的设置。在 linux 上,gcc-ffast-数学应设置突然下溢 (但我认为,不在 Windows 上)。英特尔编译器被应该在 main () 中初始化,以便不通过这些操作系统的差异,但我已经被 bitten,并需要在程序中显式设置。英特尔 Cpu 开头 Sandy Bridge 是应该处理 subnormals 中添加/减去损失 (但不是乘除/) 有效,所以没有使用逐步下溢的情况。

Microsoft /fp:fast (不是默认值) 不会执行任何 gcc-ffast-数学或 ICL (默认值) /fp:fast 中固有的积极事情。它很像 ICL /fp:source。因此,您必须设置 /fp: (和,在某些情况下下, 溢模式) 明确,如果您想要比较这些编译器。

请输入您的翻译

Why does changing 0.1f to 0 slow down performance by 10x?

确认取消