【问题标题】:Floating Point Div/Mul > 30 times slower than Add/Sub?浮点 Div/Mul > 比 Add/Sub 慢 30 倍?
【发布时间】:2011-03-18 14:20:04
【问题描述】:

我最近阅读了这篇文章: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;
}

这里有什么想法吗?

【问题讨论】:

  • 您在 C++ 中使用了哪些优化设置?您是否在 Visual Studio 的测试主机中运行此程序(时间对我来说似乎很慢......)?
  • 它们对我来说似乎也很慢。我从命令行运行它,完整程序优化,优化速度。 32 位 Windows XP。
  • 当我尝试运行您的基准测试时,我得到了非常奇怪的结果,它似乎完全随机。有一次,doublefloat 的除法约为 200 毫秒,下一次可能高达 7000 毫秒。我运行它的迭代次数减少了 10 倍,否则当它出现时会花费很长时间。那是在 C# 方面,在 C++ 方面,我看到 float add/sub 比 C# 慢 3 倍,并且除法在 7+ 秒时一直很慢。
  • @JulianR:这就是我在这里发布这个问题的原因,我不太明白为什么会有奇怪的性能差异。

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


【解决方案1】:

如果您对浮点速度和可能的优化感兴趣,请阅读本书: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 寄存器更多的变量。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        您应该能够将其添加到 C++ 的开头以将非规范化刷新为零:

        _controlfp(_DN_FLUSH, _MCW_DN);
        

        我不确定如何在 C# 中执行此操作(或者是否可能)。

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

        【讨论】:

        • 这解决了它。将该行添加到 GenericTest 函数使其执行得更加合理。浮点加 1 秒,浮点 mul/div 1.3 秒,双加 1 秒,双 mul/div 1.6 秒。
        • 我仍然不确定它是否是一个有缺陷的基准,但至少现在它较少有缺陷:)
        【解决方案5】:

        我还认为您的 C++ 速度非常慢。所以我自己跑了。事实证明,你完全错了。

        我用 Windows 高性能计时器替换了您的计时器(我不知道您使用的是什么计时器,但我没有一个方便的计时器)。那件事可以做到纳秒或更好。你猜怎么了? Visual Studio 说不。我什至没有调整它以获得最高性能。 VS 可以看穿这种废话并省略所有循环。这就是为什么你永远不应该使用这种“分析”。找一个专业的分析器然后回来。除非 2010 Express 与 2010 Professional 不同,我对此表示怀疑。它们的主要区别在于 IDE 功能,而不是原始代码性能/优化。

        我什至不会费心运行你的 C#。

        编辑:这是 DEBUG x64(之前的屏幕是 x86,但我想我会做 x64,因为我在 x64 上),我还修复了一个小错误,导致时间是负数而不是正数。因此,除非您想告诉我您在 32 位上发布的 FP 慢了一百倍,否则我认为您搞砸了。

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

        【讨论】:

        • 嗯 0 纳秒,你用的是 nasa pc 吗?
        • @PoweRoy:阅读帖子。关键是编译器优化了所有这些。
        • @SoapBox:OP 已经发布了我的代码。坦率地说,我只是懒得把结果打出来。
        • @DeadMG 为什么第一个和第二个时间不一样?第一个只显示 0(你没有解释原因)。
        • @PoweRoy:......也许你可以阅读这篇文章。第二次运行是在 DEBUG 模式下,即禁用所有编译器优化和额外的调试开销,它仍然比 OP 的时间快一百倍。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-22
        • 1970-01-01
        • 2022-01-14
        • 2018-11-03
        • 2015-07-18
        相关资源
        最近更新 更多