关于c#:Floating Point Div / Mul>

Floating Point Div/Mul > 30 times slower than Add/Sub?

我最近读了这篇文章:关于现代硬件的浮点运算和整数运算,并且对我自己的处理器在这个准基准上的性能感到好奇,所以我把代码的两个版本放在一起,一个在C中,一个在C++中(Visual Studio 2010 Express),并用优化的方法编译它们,看看到底出了什么问题。"我的C"版本的输出相当合理:

1
2
3
4
5
6
int add/sub: 350ms
int div/mul: 3469ms
float add/sub: 1007ms
float div/mul: 67493ms
double add/sub: 1914ms
double div/mul: 2766ms

当我编译并运行C++版本时,一些完全不同的东西震动了:

1
2
3
4
5
6
int add/sub: 210.653ms
int div/mul: 2946.58ms
float add/sub: 3022.58ms
float div/mul: 172931ms
double add/sub: 1007.63ms
double div/mul: 74171.9ms

我期望有一些性能差异,但不是这么大!我不明白为什么C++中的除法/乘法比加减法慢得多,其中托管C版本更符合我的预期。该函数的C++版本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
template< typename T> void GenericTest(const char *typestring)
{
    T v = 0;
    T v0 = (T)((rand() % 256) / 16) + 1;
    T v1 = (T)((rand() % 256) / 16) + 1;
    T v2 = (T)((rand() % 256) / 16) + 1;
    T v3 = (T)((rand() % 256) / 16) + 1;
    T v4 = (T)((rand() % 256) / 16) + 1;
    T v5 = (T)((rand() % 256) / 16) + 1;
    T v6 = (T)((rand() % 256) / 16) + 1;
    T v7 = (T)((rand() % 256) / 16) + 1;
    T v8 = (T)((rand() % 256) / 16) + 1;
    T v9 = (T)((rand() % 256) / 16) + 1;

    HTimer tmr = HTimer();
    tmr.Start();
    for (int i = 0 ; i < 100000000 ; ++i)
    {
        v += v0;
        v -= v1;
        v += v2;
        v -= v3;
        v += v4;
        v -= v5;
        v += v6;
        v -= v7;
        v += v8;
        v -= v9;
    }
    tmr.Stop();

      // I removed the bracketed values from the table above, they just make the compiler
      // assume I am using the value for something do it doesn't optimize it out.
    cout << typestring <<" add/sub:" << tmr.Elapsed() * 1000 <<"ms [" << (int)v <<"]" << endl;

    tmr.Start();
    for (int i = 0 ; i < 100000000 ; ++i)
    {
        v /= v0;
        v *= v1;
        v /= v2;
        v *= v3;
        v /= v4;
        v *= v5;
        v /= v6;
        v *= v7;
        v /= v8;
        v *= v9;
    }
    tmr.Stop();

    cout << typestring <<" div/mul:" << tmr.Elapsed() * 1000 <<"ms [" << (int)v <<"]" << endl;
}

C测试的代码不是通用的,因此可以实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static double DoubleTest()
{
    Random rnd = new Random();
    Stopwatch sw = new Stopwatch();

    double v = 0;
    double v0 = (double)rnd.Next(1, int.MaxValue);
    double v1 = (double)rnd.Next(1, int.MaxValue);
    double v2 = (double)rnd.Next(1, int.MaxValue);
    double v3 = (double)rnd.Next(1, int.MaxValue);
    double v4 = (double)rnd.Next(1, int.MaxValue);
    double v5 = (double)rnd.Next(1, int.MaxValue);
    double v6 = (double)rnd.Next(1, int.MaxValue);
    double v7 = (double)rnd.Next(1, int.MaxValue);
    double v8 = (double)rnd.Next(1, int.MaxValue);
    double v9 = (double)rnd.Next(1, int.MaxValue);

    sw.Start();
    for (int i = 0; i < 100000000; i++)
    {
        v += v0;
        v -= v1;
        v += v2;
        v -= v3;
        v += v4;
        v -= v5;
        v += v6;
        v -= v7;
        v += v8;
        v -= v9;
    }
    sw.Stop();

    Console.WriteLine("double add/sub: {0}", sw.ElapsedMilliseconds);
    sw.Reset();

    sw.Start();
    for (int i = 0; i < 100000000; i++)
    {
        v /= v0;
        v *= v1;
        v /= v2;
        v *= v3;
        v /= v4;
        v *= v5;
        v /= v6;
        v *= v7;
        v /= v8;
        v *= v9;
    }
    sw.Stop();

    Console.WriteLine("double div/mul: {0}", sw.ElapsedMilliseconds);
    sw.Reset();

    return v;
}

有什么想法吗?


对于float div/mul测试,您可能会得到非规范化的值,这比处理正常的浮点值慢得多。这对于int测试来说不是问题,而且对于双重测试来说会在很晚出现。

您应该能够将此添加到C++的开始,以将反义词刷新为零:

1
_controlfp(_DN_FLUSH, _MCW_DN);

不过,我不知道如何用C语言来完成(或者如果可能的话)。

此处提供更多信息:浮点数学执行时间


有可能C将vx的除法优化为1 / vx的乘法,因为它知道这些值在循环过程中不会被修改,并且它可以预先计算一次反比。

你可以自己做这个优化,然后在C++中完成。


如果您对浮点速度和可能的优化感兴趣,请阅读本书:http://www.agner.org/optimize/optimizing_cpp.pdf

您还可以查看:http://msdn.microsoft.com/en-us/library/aa289157%28vs.71%29.aspx

结果可能取决于诸如JIT、编译标志(调试/发布、要执行的FP优化类型或允许的指令集)等内容。

尝试将这些标志设置为最大优化并更改您的程序,这样它肯定不会产生溢出或NaN,因为它们会影响计算速度。(即使是"v+=v1;v+=v2;v-=v1;v-=v2";也可以,因为它不会在"严格"或"精确"浮点模式下减少)。另外,尽量不要使用超过fp寄存器数量的变量。


乘法也不错。我认为它比加法慢几个周期,但是的,除法比其他的要慢得多。这需要更长的时间,而且与其他3个操作不同,它不是流水线操作。


我还认为你的C++速度非常慢。所以我自己做的。事实上,你完全错了。失败http://img59.imageshack.us/img59/3597/loltimer.jpg

我用Windows高性能计时器替换了你的计时器(我不知道你用的是什么计时器,但我手头没有)。那东西可以做纳秒甚至更好。你猜怎么着?Visual Studio拒绝了。我甚至没有调整它以获得最高性能。vs可以看穿这类垃圾,省略所有循环。这就是为什么你永远不应该使用这种"剖析"。找个专业的分析人员回来。除非2010年快递与2010年专业版不同,我对此表示怀疑。它们主要在IDE特性上有所不同,而不是原始代码性能/优化。

我甚至不想麻烦你管理你的C。

编辑:这是调试x64(前一个屏幕是x86,但我想我会做x64,因为我在x64上),我还修复了一个小错误,导致时间为负而不是正。所以,除非你想告诉我你的32位FP版本慢了一百倍,否则我认为你搞砸了。alt文本http://img693.imageshack.us/img693/1866/loltimerdebug.jpg

有一件事我很好奇,那就是x86调试程序从来没有在第二个float测试中终止,也就是说,如果您先执行float,然后执行double,那么失败的是double-div/mul。如果您执行了Double,那么float DIV/MUL失败。一定是编译器故障。