2010-07-20 58 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 

重新排序的電話顯示了相同的趨勢,以及。食物的思想。

相關問題