2014-07-05 15 views
12

我喜歡F#的一件事是一個真實的inline關鍵字。然而,雖然它允許編寫與粘貼代碼塊相同的一階函數,但對於高階函數來說事情並不那麼樂觀。考慮爲什麼F#編譯器不能完全嵌入高階函數的函數參數?

let inline add i = i+1 
let inline check i = if (add i) = 0 then printfn ""  
let inline iter runs f = for i = 0 to runs-1 do f i 
let runs = 100000000 
time(fun()->iter runs check) 1 
time(fun()->for i = 0 to runs-1 do check i) 1 

結果是244 msiter61 ms用於手動檢查。讓我們深入研究ILSpy。要求直接呼叫的相關功能是:

internal static void [email protected](Microsoft.FSharp.Core.Unit unitVar0) 
{ 
    for (int i = 0; i < 100000000; i++) 
    { 
     if (i + 1 == 0) 
     { 
      Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>(""); 
      Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format); 
     } 
    } 
} 

With add內聯。對於iter相關功能

internal static void [email protected](Microsoft.FSharp.Core.Unit unitVar0) 
{ 
    for (int i = 0; i < 100000000; i++) 
    { 
     [email protected](i); 
    } 
} 
internal static void [email protected](int i) 
{ 
    if (i + 1 == 0) 
    { 
     Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit> format = new Microsoft.FSharp.Core.PrintfFormat<Microsoft.FSharp.Core.Unit, System.IO.TextWriter, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit, Microsoft.FSharp.Core.Unit>(""); 
     Microsoft.FSharp.Core.PrintfModule.PrintFormatLineToTextWriter<Microsoft.FSharp.Core.Unit>(System.Console.Out, format); 
     return; 
    } 
} 

,我們可以看到的性能損失來自一個間接額外的水平。正如性能測試顯示的那樣,JIT編譯器也不會去除這種間接性。是否有理由爲什麼高階函數不能完全內聯?編寫計算內核時這是一件很痛苦的事情。

我的時間組合子(雖然這裏並不真正相關)是

let inline time func n = 
    func() |> ignore 
    GC.Collect() 
    GC.WaitForPendingFinalizers() 
    let stopwatch = Stopwatch.StartNew() 
    for i = 0 to n-1 do func() |> ignore 
    stopwatch.Stop() 
    printfn "Took %A ms" stopwatch.Elapsed.TotalMilliseconds 
+0

請確認您是否在發佈模式下運行此程序時未附加調試器。除此之外,基準似乎有效。您可以通過將工作量增加10倍來消除一次性成本的影響。 – usr

+0

@usr是的,我運行它沒有調試器,並在發佈模式編譯。毫無疑問,性能差異是真實的,因爲它可以從IL代碼中推導出來(禁止JIT優化)。 – Arbil

+0

@Arbil我掛這個問題上有關內聯分析,F#語言設計UserVoice的主題之一:https://fslang.uservoice.com/forums/245727-f-language/suggestions/6137978-better-inlining-analysis -and-啓發式算法 –

回答

6

只是要清楚,F#編譯器的內聯每次您已標記爲inline定義。只是當使用內聯函數作爲高階參數時,內聯的當前行爲不是很有用。給定的一個參數時,等等iter runs check被視爲iter runs (fun i -> check i)check只能被內聯。然後check得到內聯,導致

iter runs (fun i -> if (add i) = 0 then printfn "") 

等效(你可以在IL看到,有在生成的IL到check沒有呼叫,但有一種叫合成[email protected]機構本拉姆達,這相當於)。 iter也被內聯了。

話雖如此,我認爲,當前的行爲並不像它可能是有用 - 編譯器也可以內嵌在lambda的身體進入調用點,這將是安全和提高性能。

+4

嚴格地說,問題不是非內聯的'check',而是'check'派生的非內聯函數。但是,據我所知,這不是我的例子特有的,而是發生在所有更高階的函數調用中。因此,性能方面與沒有內聯的函數參數相同。爲什麼我們(那些對使用F#進行高性能/科學/遊戲開發感興趣的人)推動解決其中一些問題?第一個是結構元組,這不是。 2.目前在fslang上,提議的績效相關提案很少。 – Arbil

+1

我很樂意繼續與您一起進行性能超羣的狂歡。高級語言是驚人的,但「性能無關緊要」的態度導致了一個反良性循環,至關重要。 – jackmott