2013-09-22 47 views
5

當我比較IL F#爲seq{}表達式生成的代碼與用戶定義的計算工作流程相比時,很明顯seq{}的實現方式非常不同:它生成一個狀態機器類似於C#用於其迭代器方法的機器。另一方面,用戶定義的工作流將按照您的期望使用相應的構建器對象。F#:爲seq {}生成的IL代碼vs其他計算工作流程

所以我想知道 - 爲什麼區別?

這是由於歷史原因,例如, 「工作流程之前有序嗎?」?
或者,是否有顯着的表現可以獲得?
其他一些原因?

回答

6

這是由F#編譯器執行的優化。據我所知,後來實際上已經實現了它--F#編譯器首先有列表解析,然後是通用版本的計算表達式(也用於seq { ... }),但效率較低,所以優化在某些更新的版本中添加。

主要原因是這消除了許多分配和間接。比方說,你有這樣的:

seq { for i in input do 
     yield i 
     yield i * 10 } 

當使用計算表達式,這被轉換爲類似:

seq.Delay(fun() -> seq.For(input, fun i -> 
    seq.Combine(seq.Yield(i), seq.Delay(fun() -> seq.Yield(i * 10))))) 

有一對夫婦的功能分配和For循環總是需要調用拉姆達功能。優化將其轉換爲狀態機(類似於C#狀態機),因此對生成的枚舉器的MoveNext()操作只是改變了某個類的狀態,然後返回...

您可以通過定義對序列的自定義計算建設者:

type MSeqBuilder() = 
    member x.For(en, f) = Seq.collect f en 
    member x.Yield(v) = Seq.singleton v 
    member x.Delay(f) = Seq.delay f 
    member x.Combine(a, b) = Seq.concat [a; b] 
let mseq = MSeqBuilder() 
let input = [| 1 .. 100 |] 

現在我們就可以進行測試(使用#time在F#互動):

for i in 0 .. 10000 do 
    mseq { for x in input do 
      yield x 
      yield x * 10 } 
    |> Seq.length |> ignore 

在我的電腦,使用自定義012時,這需要2.644sec構建器,但使用內置優化seq表達式時僅0.065秒。所以優化使得序列表達式更加高效。

+0

值得注意的是,你可以'內聯'你的自定義構建器方法來獲得一些優化。 – t0yv0

+0

@TomasPetricek:有什麼方法可以重寫MSeqBuilder,使其生成更接近優化狀態機版本的代碼? – user1411900

+0

@toyvo這是一個很好的觀點。這應該會讓它變得更快。 –

0

歷史上,計算表達式(「工作流程」)是序列表達式的一般化:http://blogs.msdn.com/b/dsyme/archive/2007/09/22/some-details-on-f-computation-expressions-aka-monadic-or-workflow-syntax.aspx

但是,答案肯定是有顯着的表現。我無法找到任何固定鏈接(雖然在http://blogs.msdn.com/b/dsyme/archive/2007/11/30/full-release-notes-for-f-1-9-3-7.aspx中提到了「在'順序表達式中過濾'時與'相關的優化'),但我確實記得這是一種優化,它在某個點時間。我想說,好處是不言而喻的:序列表達式是一種「核心」語言特性,並且可以進行任何優化。

同樣,你會看到某些尾遞歸函數將優化到循環,而不是尾調用。