浮点Div / Mul>比Add / Sub慢30倍?

时间:2010-07-20 17:54:57

标签: c# c++ performance floating-point

我最近阅读了这篇文章:Floating point vs integer calculations on modern hardware并对我自己的处理器在这个准基准测试中的性能感到好奇,因此我将两个版本的代码放在一起,一个在C#中,一个在C ++中(Visual Studio 2010 Express)并通过优化对它们进行编译,以查看其中的内容。我的C#版本的输出是相当合理的:

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 ++版本时,一些完全不同的东西被淘汰了:

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 ++版本的代码如下:

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#测试的代码不是通用的,因此实现:

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;
}

这里有什么想法吗?

5 个答案:

答案 0 :(得分:4)

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

你应该能够将它添加到C ++的开头,将非正规数刷新为零:

_controlfp(_DN_FLUSH, _MCW_DN);

我不知道如何在C#中做到这一点(或者如果可能的话)。

这里有更多信息: Floating Point Math Execution Time

答案 1 :(得分:3)

C#可能会将除法vx优化为乘以1 / vx,因为它知道这些值在循环期间未被修改,并且它可以预先计算一次反转。

您可以自己进行优化并在C ++中计时。

答案 2 :(得分:2)

如果您对浮点速度和可能的优化感兴趣,请阅读本书: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 :(得分:1)

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

答案 4 :(得分:0)

我还认为你的C ++非常慢。所以我自己跑了。事实证明,实际上,你完全错了。 fail http://img59.imageshack.us/img59/3597/loltimer.jpg

我用Windows高性能计时器替换了你的计时器(我不知道你使用的计时器,但我没有一个方便)。那东西可以做纳秒或更好。你猜怎么着? Visual Studio说没有。我甚至没有调整它以获得最高性能。 VS可以通过这种废话看到并消除所有循环。这就是为什么你永远不应该使用这种“剖析”。获得专业的探查器并回来。除非2010 Express与2010 Professional不同,我怀疑。它们主要在IDE功能上有所不同,而不是原始代码性能/优化。

我甚至不打扰你的C#。

编辑:这是DEBUG x64(上一个屏幕是x86,但我认为我在x64上做x64)并且我还修复了一个小错误,导致时间是负面而不是正面。因此,除非你想告诉我你的32位版本的FP慢了一百倍,我想你已经搞砸了。 alt text http://img693.imageshack.us/img693/1866/loltimerdebug.jpg

我确实感到好奇的一件事是x86调试程序从未在第二次浮动测试中终止,即,如果你先浮动,然后加倍,那就是失败的双div / mul。如果你做了双倍然后浮动,浮动div / mul失败。必须是编译器故障。