与浮点数不一致的乘法性能

时间:2012-12-20 02:56:24

标签: c# .net performance floating-point

在.NET中测试浮点数的性能时,我偶然发现了一个奇怪的情况:对于某些值,乘法似乎比正常慢。以下是测试用例:

using System;
using System.Diagnostics;

namespace NumericPerfTestCSharp {
    class Program {
        static void Main() {
            Benchmark(() => float32Multiply(0.1f), "\nfloat32Multiply(0.1f)");
            Benchmark(() => float32Multiply(0.9f), "\nfloat32Multiply(0.9f)");
            Benchmark(() => float32Multiply(0.99f), "\nfloat32Multiply(0.99f)");
            Benchmark(() => float32Multiply(0.999f), "\nfloat32Multiply(0.999f)");
            Benchmark(() => float32Multiply(1f), "\nfloat32Multiply(1f)");
        }

        static void float32Multiply(float param) {
            float n = 1000f;
            for (int i = 0; i < 1000000; ++i) {
                n = n * param;
            }
            // Write result to prevent the compiler from optimizing the entire method away
            Console.Write(n);
        }

        static void Benchmark(Action func, string message) {
            // warm-up call
            func();

            var sw = Stopwatch.StartNew();
            for (int i = 0; i < 5; ++i) {
                func();
            }
            Console.WriteLine(message + " : {0} ms", sw.ElapsedMilliseconds);
        }
    }
}

结果:

float32Multiply(0.1f) : 7 ms
float32Multiply(0.9f) : 946 ms
float32Multiply(0.99f) : 8 ms
float32Multiply(0.999f) : 7 ms
float32Multiply(1f) : 7 ms

为什么param = 0.9f的结果如此不同?

测试参数:.NET 4.5,发布版本,代码优化ON,x86,未附加调试器。

2 个答案:

答案 0 :(得分:6)

正如其他人所提到的,当涉及低于正常的浮点值时,各种处理器不支持正常速度计算。这可能是一个设计缺陷(如果行为损害了您的应用程序或者其他方面很麻烦)或一个功能(如果您更喜欢更便宜的处理器或通过不使用门来实现这项工作而使用的硅的替代使用)。

了解为什么在.5:

有转变是很有启发性的

假设您乘以 p 。最终,该值变得很小,结果是一些次正常值(在32位IEEE二进制浮点下低于2 -126 )。然后乘法变慢。当您继续相乘时,该值继续递减,并且达到2 -149 ,这是可以表示的最小正数。现在,当你乘以 p 时,确切的结果当然是2 -149 p ,它介于0和2之间 - 149 ,这是两个最接近的可表示值。机器必须对结果进行舍入并返回这两个值中的一个。

哪一个?如果 p 小于1/2,则2 -149

接近0而不是2 -149 ,所以机器返回0.然后你不再使用次正规值了,乘法再次快。如果 p 大于1/2,那么2 -149

更接近2 -149 而不是0,所以机器返回2 -149 ,你继续使用次正规值,乘法仍然很慢。如果 p 恰好是½,则舍入规则表示使用其有效数的低位(零部分)中为零的值,该值为零(2 -149 低位有1个。)

您报告.99f看起来很快。这应该以缓慢的行为结束。也许您发布的代码不完全是您使用.99f测量快速性能的代码?也许起始值或迭代次数发生了变化?

有办法解决这个问题。一个是硬件具有模式设置,其指定将所使用或获得的任何次正常值改变为零,称为“非正常为零”或“齐射到零”模式。我不使用.NET,也无法告诉您如何在.NET中设置这些模式。

另一种方法是每次添加一个微小的值,例如

n = (n+e) * param;

其中e至少为2 -126 / param。请注意,2 -126 / param应计算向上舍入,除非您可以保证n足够大(n+e) * param不产生次正常值。这也假设n不是否定的。这样做的结果是确保计算值总是大到足以在正常范围内,从不低于正常范围。

以这种方式添加e当然会改变结果。但是,如果您正在处理具有某种回声效果(或其他滤波器)的音频,则e的值太小而不会导致人们听到音频时观察到的任何效果。在生成音频时,它可能太小而不会导致硬件行为发生任何变化。

答案 1 :(得分:2)

我怀疑这与非正规值(fp值小于~1e-38)以及与处理它们相关的成本有关。

如果您测试非正常值并将其删除,则恢复健全。

    static void float32Multiply(float param) {
        float n = 1000f;
        int zeroCount=0;
        for (int i = 0; i < 1000000; ++i) {
            n = n * param;
            if(n<1e-38)n=0;
        }
        // Write result to prevent the compiler from optimizing the entire method away
        Console.Write(n);
    }
相关问题