c++ 浮点Div/穆尔比Add/Sub慢30倍?

bsxbgnwa  于 2023-07-01  发布在  其他
关注(0)|答案(5)|浏览(98)

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

有什么想法吗?

kpbpu008

kpbpu0081#

对于float div/穆尔测试,您可能会得到非规范化的值,处理正常的浮点值要慢得多。这对于int测试来说不是问题,对于double测试来说会在很久以后出现。
你应该能够将它添加到C++的开头,以将反正规值刷新为零:

_controlfp(_DN_FLUSH, _MCW_DN);

我不知道如何在C#中实现(或者是否可能)。
这里有更多信息:Floating Point Math Execution Time

9nvpjoqh

9nvpjoqh2#

C#可能将vx的除法优化为1 / vx的乘法,因为它知道这些值在循环期间不会修改,并且它可以预先计算一次逆。
你可以自己做这个优化,并在C++中计时。

jk9hmnmh

jk9hmnmh3#

如果你对浮点运算速度和可能的优化感兴趣,请阅读这本书:http://www.agner.org/optimize/optimizing_cpp.pdf
你也可以检查这个:Microsoft Visual C++ Floating-Point Optimization
您的结果可能取决于JIT、编译标志(调试/发布、执行何种FP优化或允许的指令集)等因素。
尝试将这些标志设置为最大优化并更改您的程序,以便它肯定不会产生溢出或NAN,因为它们会影响计算速度。(甚至像v += v1; v += v2; v -= v1; v -= v2;这样的也可以,因为它不会在strictprecise浮点模式下减少)。另外,尽量不要使用比FP寄存器更多的变量。

sc4hvdpw

sc4hvdpw4#

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

n7taea2i

n7taea2i5#

我还发现你的C++非常慢。所以我自己查了。事实证明你完全错了

我用Windows高性能计时器替换了你的计时器(我不知道你用的是什么计时器,但我手边没有)。那东西能做到纳秒甚至更快。你猜怎么着?Visual Studio说不。我甚至没有调整它的最高性能。VS可以看穿这类废话,并省略了所有的循环。这就是为什么你永远不应该使用这种“侧写”。找个专业的侧写师回来。除非2010年快车和2010年专业版不一样,这一点我很怀疑。它们主要在IDE功能上有所不同,而不是原始代码性能/优化。
我甚至不打算运行你的C#。
编辑:这是调试x64(以前的屏幕是x86,但我想我会做x64,因为我在x64),我还修复了一个小错误,导致时间是负的,而不是积极的。所以除非你想告诉我你的32位版本的FP慢了一百倍,否则我认为你搞砸了。

有一件事我确实感到好奇的是,x86调试程序从来没有在第二次浮点测试时终止,也就是说,如果你先float,然后double,失败的是double div/穆尔。如果你先double然后float,float div/穆尔失败。一定是编译器出了问题。

相关问题