我可以改进嵌入式C#上的“double.IsNaN(x)”函数调用吗?

时间:2010-07-20 02:05:08

标签: c# performance

我在for循环中调用了数百万次代码,检查传递的参数是否为double.NaN。我描述了我的应用程序,其中一个瓶颈就是这个简单的功能:

public void DoSomething(double[] args)
{
    for(int i = 0; i < args.Length;i++)
    {
       if(double.IsNan(args[i]))
       {
         //Do something
       }
    }
}

即使我无法更改if

中的代码,我是否可以优化它?

3 个答案:

答案 0 :(得分:18)

如果你真的优化了代码的其他部分,你可以让这个函数变得有点神秘,利用非数字(NaN)的定义:

  

“谓词x!= y是真的但是全部   其他人,x&lt; y,x&lt; = y,x == y,x&gt; =   y和x&gt; y,只要x或者为假   是或两者都是NaN。“(IEEE标准754   for Binary Floating-Point Arithmetic)

将其翻译成您的代码:

public void DoSomething(double[] args)
{
    for(int i = 0; i < args.Length;i++)
    {
       double value = args[i];  
       if(value != value)
       {
         //Do something
       }
    }
}

在使用WindoWs CE + .NET Compact Framework 3.5的ARM设备中,获得Nan的概率约为50%,value!= value的速度是double.IsNan(value)的两倍。

确保衡量您的应用程序执行后!

答案 1 :(得分:4)

我觉得很难(但并非不可能)相信args[i]上的任何其他检查都会比double.IsNan()更快。

一种可能性是这是一个功能。调用函数有一些开销,有时很大,特别是如果函数本身相对较小的话。

您可以利用以下事实:IEEE754 NaN的位模式是众所周知的,只需进行一些位检查(没有调用函数来执行此操作) - 这将消除该开销。在C中,我会尝试使用宏。如果指数位都是1且尾数位都不是0,那就是NaN(信号或静默由符号位决定,但你可能并不关心它)。此外,NaNs永远不会彼此相等,因此您可以测试args[i]与其自身之间的相等性 - false表示它是NaN。

如果阵列的使用频率高于其更改,则另一种可能性是可行的。保持另一个布尔数组,指示相关的双数是否为NaN。然后,每当其中一个双精度发生变化时,计算相关的布尔值。

然后你的功能变为:

public void DoSomething(double[] args, boolean[] nan) {
    for(int i = 0; i < args.Length; i++) {
       if (nan[i]) {
         //Do something
       }
    }
}

这与数据库中使用的“技巧”相同,只有在数据发生变化时才会预先计算值,而不是每次读出数据时。如果你的数据被使用的情况比被改变的要多得多,那么这是一个很好的优化(大多数算法可以在时间上交换空间)。

但请记住优化口头禅:测量,不要猜测!

答案 2 :(得分:1)

为了进一步重申性能测试的重要性,我在使用VS 2010目标.NET 4.0编译的Windows 7上以64位本机和32位模式在我的Core i5-750上运行了以下测试,并获得了以下结果:

 public static bool DoSomething(double[] args) {
        bool ret = false;
        for (int i = 0; i < args.Length; i++) {
            if (double.IsNaN(args[i])) {
                ret = !ret;
            }
        }
        return ret;
    }

    public static bool DoSomething2(double[] args) {
        bool ret = false;
        for (int i = 0; i < args.Length; i++) {
            if (args[i] != args[i]) {
                ret = !ret;
            }
        }
        return ret;
    }

    public static IEnumerable<R> Generate<R>(Func<R> func, int num) {
        for (int i = 0; i < num; i++) {
            yield return func();
        }
    }

    static void Main(string[] args) {
        Random r = new Random();
        double[] data = Generate(() => {
            var res = r.NextDouble();
            return res < 0.5 ? res : Double.NaN;
        }, 1000000).ToArray();

        Stopwatch sw = new Stopwatch();

        sw.Start();
        DoSomething(data);
        Console.WriteLine(sw.ElapsedTicks);

        sw.Reset();
        sw.Start();
        DoSomething2(data);
        Console.WriteLine(sw.ElapsedTicks);

        Console.ReadKey();
    }

在x86模式下(自然发布):

DoSomething() = 139544
DoSomething2() = 137924

在x64模式下:

DoSomething() = 19417
DoSomething2() = 17448

然而,如果NaN的分布较为稀疏,会发生一些有趣的事情。如果我们将0.5常量更改为0.9(只有10%NaN),我们会得到:

86:

DoSomething() = 31483
DoSomething2() = 31731

64:

DoSomething() = 31432
DoSomething2() = 31513

重新排序电话也显示出相同的趋势。值得深思。