双精度数据类型比浮点数据类型更快地执行简单的数学运算?

3

可能有重复:
C#中,double比float更快吗?

我编写了一个简单的基准测试来检查在我的应用程序中将double数据类型更改为float可以获得多少性能。这是我的代码:

    // my form: 
    // one textbox: textbox1 (MultiLine property set to true)
    // one button: button1 with event button1_Click

    private void button1_Click(object sender, EventArgs e)
    {

        int num = 10000000;

        float[] floats1 = new float[num];
        float[] floats2 = new float[num];
        float[] floatsr = new float[num];  // array for results
        double[] doubles1 = new double[num];
        double[] doubles2 = new double[num];
        double[] doublesr = new double[num]; // array for results

        Stopwatch stw = new Stopwatch();

        log("Preparing data");

        Random rnd = new Random();

        stw.Start();

        for (int i = 0; i < num; i++)
        {
            floats1[i] = NextFloat(rnd);
            floats2[i] = NextFloat(rnd);
            doubles1[i] = rnd.NextDouble();
            doubles2[i] = rnd.NextDouble();
        }
        stw.Stop();
        log(stw.Elapsed.TotalMilliseconds.ToString()+"ms");
        stw.Reset();




        log("");


        stw.Start();
        for (int i = 0; i <# i++)
        {
            floatsr[i] = floats1[i] * floats2[i];
        }
        stw.Stop();
        log("Multiplying floats: " + stw.Elapsed.TotalMilliseconds.ToString() + "ms");
        stw.Reset();



        stw.Start();
        for (int i = 0; i < num; i++)
        {
            doublesr[i] = doubles1[i] * doubles2[i];
        }
        stw.Stop();
        log("Multiplying doubles: " + stw.Elapsed.TotalMilliseconds.ToString() + "ms");
        stw.Reset();


        stw.Start();
        for (int i = 0; i < num; i++)
        {
            floatsr[i] = floats1[i] / floats2[i];
        }
        stw.Stop();
        log("Dividing floats: " + stw.Elapsed.TotalMilliseconds.ToString() + "ms");
        stw.Reset();


        stw.Start();
        for (int i = 0; i < num; i++)
        {
            doublesr[i] = doubles1[i] / doubles2[i];
        }
        stw.Stop();
        log("Dividing doubles: " + stw.Elapsed.TotalMilliseconds.ToString() + "ms");
        stw.Reset();

    }

    private void log(string text)
    {
        textBox1.Text = textBox1.Text + text + Environment.NewLine;
    }

    // I found that function somewhere on stackoverflow
    static float NextFloat(Random random)
    {
        double mantissa = (random.NextDouble() * 2.0) - 1.0;
        double exponent = Math.Pow(2.0, random.Next(-126, 128));
        return (float)(mantissa * exponent);
    }

我得到了如下结果(发布版本,无调试,英特尔移动酷睿双核T2500 2.0GHz 2MB的CPU):
Preparing data 5275,6862ms

Multiplying floats: 442,7865ms 
Multiplying doubles: 169,4028ms
Dividing floats: 550,7052ms 
Dividing doubles: 164,1607ms

我很惊讶,双精度浮点数的运算速度几乎比单精度浮点数的运算速度快3倍。我在这里搜索了“double float”,找到了这个链接:Is using double faster than float?
最佳回答侧重于CPU架构,但我不能同意这一点。
我怀疑其他原因导致了浮点数的低性能,因为我的CPU使用Intel SSE应该能够同时乘以或除以4个浮点数(打包的浮点指令)或2个双精度浮点数。所以浮点数应该更快。
也许编译器(或.NET中的CLR)以某种方式优化内存使用?
是否有任何方法可以优化并使浮点数更快?
请不要报告重复问题,我看到其他问题并不满足我。
根据Servy的建议,我更改了生成浮点数的方法后,我的结果现在看起来不错。
Preparing data 1367,0678ms

Multiplying floats: 109,8742ms 
Multiplying doubles: 149,9555ms
Dividing floats: 167,0079ms 
Dividing doubles: 168,6821ms

请记住,在任何长时间的数值计算中使用浮点数都存在积累舍入误差的风险。 - Deer Hunter
1
你的 NextFloat 生成的数字分布与 NextDouble 生成的数字分布非常不同。我不知道这是否有任何相关性。但请注意,NextDouble 生成介于 0.01.0 之间的数字,是 1.0 / 2147483647.0 的整数倍。因此,数字的“结尾”并不真正随机,这在舍入计算的乘积或商时可能很重要。 - Jeppe Stig Nielsen
@JeppeStigNielsen 这很重要;请看我的回答。将它们改为生成0到1之间的数字会彻底改变我在修改前后运行OP代码时的运行时间。 - Servy
1
另外,假设您的计算机可以将64位浮点数相乘。为了处理32位(floatSystem.Single情况),它可能需要先从数组中读取“扩展”数字,然后进行乘法运算,最后舍入并“缩小”数字以适应结果数组的32位“插槽”。而64位数字则不需要进行任何转换。 - Jeppe Stig Nielsen
2个回答

6

这与您生成随机数的方式有关。浮点数的乘除法不完全相同;这些数字的实际值很重要。对于浮点数而言,您正在填充一个相当大范围的值。如果您创建的浮点数介于0和1之间,就像双精度浮点数一样,那么结果更符合预期。只需将 NextFloat 更改为以下内容:

static float NextFloat(Random random)
{
    return (float) random.NextDouble();
}

我刚刚进行了一些测试,通过这个变化,浮点数的乘法速度提高了33%。

当然,这只是最简单的比较方式。要更好地了解浮点数与双精度浮点数的差异,您需要生成随机浮点数和双精度浮点数,在各自类型的完整范围内进行比较,或者更好的是,两个都持有表示程序将使用的数据类型所代表的值。


1
这是正确的。如果您进行此更改,则浮点性能将比双精度性能略好。 - Pete
好的发现!修复后的时间:浮点数乘法:107,5963毫秒,双精度浮点数乘法:132,2323毫秒,浮点数除法:160,1531毫秒,双精度浮点数除法:170,8343毫秒。现在我明白了。 - Kamil
也许更公平地说:static float NextFloat(Random random) { return random.Next(32767) / 32767f; } 的原因是 NextDouble 并没有使用 double 的全部精度。它只产生一个 1.0 / 2147483647.0 倍数的数字,大约有十个小数位的精度,而 double 的精度约为十六个小数位。 - Jeppe Stig Nielsen

4

在某些情况下,GPU对浮点数的操作仍然比CPU快得多,因为它们具有32位浮点硬件。

您的x86(或x86_64)架构CPU在数学协处理器中没有32位支持。甚至没有64位支持。x87浮点单元使用80位算术。

现代x86 CPU确实具有SIMD指令(MMX,SSE,AVX),可支持32位和64位浮点运算,并且性能更高——如果您可以在SIMD单元中执行所有操作。在SIMD和FPU之间移动数据会显著降低性能。

截至当前版本,.NET不使用MMX或SSE或AVX。您可以尝试Mono,该软件提供了可以JIT编译为SIMD指令的内在方法。或者您可以使用本地代码进行性能敏感部分的操作,因为现代C ++编译器不仅允许使用SIMD,而且可以将普通代码自动矢量化为SIMD指令。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接