2010-07-20 57 views
4

我在for循环内调用了数百万次的代码行,检查传递的参数是否为double.NaN。从来就异型我的应用程序和瓶颈之一就是这个简单的功能:我可以改进嵌入式C#上的“double.IsNaN(x)”函数调用吗?

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

我可以优化它,即使我不能改变if里面的代码?

回答

18

如果你真的优化了代码的其他部分,你可以让这个功能变得有点神秘的利用不是一个数字的(NAN)的定义:

“谓词X = Y是真的,但所有的y或x> y,都是假的,或者两者都是NaN。(IEEE标准754 对于二进制浮点运算)

翻译,为您的代码,你会得到:使用Windows CE + .NET Compact Framework的3.5得到一个楠,值的50%左右的概率

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

在ARM设备=值两倍的速度加倍.IsNan(值)。

只要确保措施您的应用程序执行后!

4

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

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

你可以利用这个事实,即IEEE754 NaN的位模式是众所周知的,只是做一些检查(没有调用一个函数来做到这一点) - 这将消除这种开销。在C中,我会尝试使用宏。指数位全为1且尾数位不全为0的情况下,这是一个NaN(信号或安静由符号位决定,但您可能不关心)。另外,NaN永远不会彼此相等,因此您可以测试args[i]与自身之间的等同性 - false表示它是NaN。

如果数组的使用频率比更改的频率更高,则另一种可能性是可行的。维护另一组布尔值,指示相关的double是否是NaN。然后,无论何时其中一个双打变化,计算相关的布尔值。

那么你的函数变为:

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

这是同一类数据库中使用“绝招”,在那里你计算预值,只有当数据的变化,而不是每次你读出来的​​时间。如果你处于数据被使用的情况比被改变更多的情况下,这是一个很好的优化(大多数算法可以为时间换取空间)。

但是请记住优化的口头禅:措施,别猜!

+0

double.isNan实际上是一个小函数......所以,正如你所指出的,有一个开销叫它。 – Hbas 2010-07-20 02:35:47

+0

在公共中间语言(CIL)它有7行的代码: L_0000:ldarg.0 L_0001:ldarg.0 L_0002:beq.s L_0006 L_0004:ldc.i4.1 L_0005:滞留 L_0006 :ldc.i4.0 L_0007:ret – Hbas 2010-07-20 02:48:14

1

只是为了进一步重申重要的性能测试如何我跑在Windows 7与2010年靶向.NET 4.0 VS编译64位本地和32位模式下我的核心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(); 
    } 

在86模式(释放,天然地):

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 

重新排序的电话显示了相同的趋势,以及。食物的思想。

相关问题