2013-08-21 22 views
13

我和我的同事比較了傳遞lambda表達式進行工作時C#函數的速度與內聯函數關於做工時間的關係。我們發現在將lambda投影傳遞給C#select函數(例如)時會產生成本,並希望查看F#是否具有相同的問題,或者如果它做了不同的事情。爲什麼減少速度比sum或sumBy快?

無論我們原來的意圖如何,我們偶然發現了一些我們無法弄清楚的東西。在下面的例子中,我們總結名單3種不同的方式

  1. 減少
  2. 總和
  3. SumBy

module fs 

open NUnit.Framework 
open FsUnit 
open System 
open System.Diagnostics; 

[<Test>] 
let sumTest() = 
    let nums = [0..1000] 

    let repeat = 100000 

    let stopWatch = new Stopwatch() 

    stopWatch.Start() 

    let sumsReduce = 
     [ 
      for i in [0..repeat] do 
       yield List.reduce (+) nums 
     ] 

    Console.WriteLine("reduce = {0} - Time = {1}", List.head sumsReduce, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 

    let sumsSum = 
     [ 
      for i in [0..repeat] do 
       yield List.sum nums 
     ] 

    Console.WriteLine("sum = {0} - Time = {1}", List.head sumsSum, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 


    let sumsSumBy = 
     [ 
      for i in [0..repeat] do 
       yield List.sumBy id nums 
     ] 

    Console.WriteLine("sumBy = {0} - Time = {1}", List.head sumsSumBy, stopWatch.Elapsed.TotalSeconds); 
    stopWatch.Restart() 

輸出到這個樣子:

reduce = 500500 - Time = 0.2725156 
sum = 500500 - Time = 1.1183165 
sumBy = 500500 - Time = 1.1126781 

這麼明顯的減少是這裏的大贏家。在反編譯中,我可以看到減少得到降低

[Serializable] 
internal class sumsReduce\u004021\u002D1 : OptimizedClosures.FSharpFunc<int, int, int> 
{ 
    internal sumsReduce\u004021\u002D1() 
    { 
    base.\u002Ector(); 
    } 

    public override int Invoke(int x, int y) 
    { 
    return x + y; 
    } 
} 

但我很難搞清楚總結和sumBy正在做什麼。時間偏差從哪裏來?


目前的答案表明,減少快5倍,因爲最初我給減少一個未檢查操作。但是,更新的測試使用檢查操作員(從Checked模塊),我仍然得到同樣的結果

let sumsReduce = 
     [ 
      for i in [0..repeat] do 
       yield List.reduce (Checked.(+)) nums 
     ] 

通知的時間差異仍然存在

reduce = 500500 - Time = 0.274697 
sum = 500500 - Time = 1.1126796 
sumBy = 500500 - Time = 1.1370642 
+0

我很樂意相信@ildjarn說得對。 Checked算術必須將事情減慢很多。 –

+0

@OnorioCatenacci,我用檢查過的arithemtic運行測試,它沒有區別 – devshorts

回答

16

薩姆和SumBy使用一個枚舉:

while e.MoveNext() do 
     acc <- Checked.(+) acc e.Current 
    acc 

而減少使用具有優化閉合的遞歸循環:(減少的用途下的蓋的摺疊 - 倍˚F頭尾

let fold<'T,'State> f (s:'State) (list: 'T list) = 
     match list with 
     | [] -> s 
     | _ -> 
      let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f) 
      let rec loop s xs = 
       match xs with 
       | [] -> s 
       | h::t -> loop (f.Invoke(s,h)) t 
      loop s list 

使用優化的閉包通常可以提高性能。

+1

嗯,這更有意義,但在這種情況下,要點中的SumLoop函數應該比SumEnum運行得更快。 https://gist.github.com/faisalmansoor/6301115但是,List.reduce歸結爲SumLoop類似的方法,而List.Sum歸類爲SumEnum。有任何想法嗎? –

+1

我認爲這是造成差異的列表遍歷,而不是'OptimizedClosure'。使用'OptimizedClosure'和一個枚舉器只會提高性能,但在遞歸函數中使用'Checked。(+)'與List.Reduce一樣快。 –

+0

確實,優化的閉包使得更多的差異與嚴重的咖喱功能等。 – 7sharp9

7

sumsumBy使用檢查算術,但你通過未經檢查的運營商+reduce - 不完全蘋果蘋果。

+0

那麼,這是有道理的。感謝您的信息 – devshorts

+1

好吧,所以我做了一個新的測試,我不認爲這是它。我將更新問題 – devshorts

相關問題