2012-05-06 52 views
6

我最近遇到了一個意想不到的代碼優化,想檢查我對我所觀察的內容的解釋是否正確。以下是的情況另一個具有更加簡化例如:F#自動泛化和性能

let demo = 
    let swap fst snd i = 
     if i = fst then snd else 
     if i = snd then fst else 
     i 
    [ for i in 1 .. 10000 -> swap 1 i i ] 

let demo2 = 
    let swap (fst: int) snd i = 
     if i = fst then snd else 
     if i = snd then fst else 
     i 
    [ for i in 1 .. 10000 -> swap 1 i i ] 

的代碼2塊之間的唯一區別是,在第二種情況下,我明確聲明交換的參數作爲整數。然而,當我用#time在fsi中運行2個片段時,我得到:

案例1實際:00:00:00.011,CPU:00:00:00.000,GC gen0:0,gen1:0,gen2: 0
案例2真:00:00:00.004,CPU:00:00:00.015,GC GEN0:0,GEN1:0,第2代:0

即第2段運行速度比第一快3倍。這裏的絕對性能差異顯然不是問題,但如果我使用交換功能很多,它會堆積起來。

我的假設是性能受到打擊的原因是,在第一種情況下,swap是通用的並且「需要相等」,並檢查int是否支持它,而第二種情況不需要檢查任何內容。這是發生這種事的原因,還是我錯過了別的東西?更一般地說,我應該認爲自動泛化是一把雙刃劍,也就是一個可能會對性能產生意想不到影響的令人敬畏的功能?

回答

10

我覺得這通常是相同的情況下,在這個問題Why is this F# code so slow。在這個問題中,性能問題是由於需要comparison的約束引起的,在你的情況下,它是由equality約束引起的。

在這兩種情況下,編譯後的通用代碼必須使用接口(拳擊),而專門的編譯後的代碼可以直接使用IL指令進行比較或整數平等或浮點數。

兩種方式來避免性能問題是:

  • 專營代碼使用intfloat像你一樣
  • 馬克這樣編譯器自動擅長它的功能inline

對於較小的功能,第二種方法比較好,因爲它不會產生太多代碼,你仍然可以寫以通用方式運行。如果您僅將函數用於單一類型(按設計),那麼使用第一種方法可能是適當的。

+0

感謝指向另一個問題,確實非常相似。所以,如果我理解正確,標記一個函數爲內聯說:「而這個函數是通用的,創建一個特定的版本基於它使用的類型稱爲」?那麼是否有理由不將每個數學樣式函數標記爲內聯? – Mathias

+0

或者換句話說,你能否詳細說明「它不會產生太多的代碼」,我不太明白。 – Mathias

+1

@Mathias「inline」關鍵字表示編譯器將用其實現替換對函數的調用。這意味着函數的代碼將在每次調用時重複一次。對於真正長的函數,這可能會使程序集更大(或生成更長時間的JIT的更長方法)。這就是爲什麼我建議僅將這個功能用於更簡短的功能 - 您示例中的功能對我來說很短暫。 –

3

的原因不同的是,編譯器生成調用

IL_0002: call bool class [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityIntrinsic<!!0> (!!0, !!0) 
在通用版本

,而int版本只需直接比較。

如果您使用inline我懷疑,這個問題就會消失,因爲編譯器現在有額外的類型信息