Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/323.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 浮点乘法性能不一致_C#_.net_Performance_Floating Point - Fatal编程技术网

C# 浮点乘法性能不一致

C# 浮点乘法性能不一致,c#,.net,performance,floating-point,C#,.net,Performance,Floating Point,在测试.NET中浮点运算的性能时,我偶然发现了一个奇怪的情况:对于某些值,乘法似乎比正常情况慢得多。以下是测试用例: using System; using System.Diagnostics; namespace NumericPerfTestCSharp { class Program { static void Main() { Benchmark(() => float32Multiply(0.1f), "\nfloat32Mul

在测试.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);
        }
    }
}
为什么param=0.9f的结果如此不同


测试参数:.NET 4.5,发布版本,x86上的代码优化,没有附加调试程序。

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

如果测试非规范值并将其删除,则会恢复正常状态

    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);
    }
static void float32Multiply(float参数){
浮子n=1000f;
int零计数=0;
对于(int i=0;i<1000000;++i){
n=n*param;

如果(n如其他人所述,当涉及低于正常的浮点值时,各种处理器不支持正常速度计算。这可能是设计缺陷(如果该行为损害应用程序或造成其他麻烦)或功能缺陷(如果您更喜欢更便宜的处理器,或者选择不使用栅极的硅片)

理解为什么在0.5处存在过渡是很有启发性的:

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

哪一个?如果p小于½,那么2-149p比2-149更接近于0,因此机器返回0。那么您不再使用低于正常值的值,并且乘法又快了。如果p大于½,那么2-149p比0更接近于2-149,因此机器返回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
的值太小,无法引起收听音频的人可以观察到的任何影响。在生成音频时,它可能太小,无法引起硬件行为的任何变化。

对0.0到1.0之间的所有数字重复此操作,以0.01为步长,我发现0.5到1.0之间的值的系数50会降低,这两种情况都不包括仅当调试器连接到.NET 4.0上时,发布版本x86进行了优化。对于慢速值,
n
的最终值为
1.401298E-45
,而对于快速值,
0
。请注意,我在
float32Multiply()中更改了迭代次数
。现在我在没有安装调试程序的情况下也遇到了减速,但减速在
0.8
0.9
之间逐渐消失,而不是在
1.0
下方突然消失。奇怪的事情。IEEE-754单精度浮点数小于2**-126(1.1754944e-38)数量级上是非规范数。在许多情况下,浮点单元处理此类非规范数的速度非常慢。速度很可能是100倍。如果将计算映射到SSE,则有DAZ(非规范数为零)和FTZ(齐平到零)在输入和输出时分别将这些小数字视为零的模式,以避免速度减慢。您的编译器可能有一个开关来打开这些模式。通常编译器标志同时打开/关闭DAZ和FTZ。@spender迟了答复,但想回答:32位JIT使用x87,64位JIT使用SSE。Mono的行为方式相同。很好回答,@Eric…我运行了提供的代码并确认报告的.99测量值是正确的…我想知道还有什么可能导致这种不一致+1@spender:一种可能性是实现使用80位浮点进行运算,因此它的指数范围比32位浮点大得多ase可能永远不会达到80位的异常范围。增加迭代次数可能会揭示这一点。
n = (n+e) * param;