確かに、リンゴとリンゴとリンゴを正確に比較しているかどうかは不明です。しかし、私は特に、差があれば、その差が軽微な場合には、その驚くべき差に驚いています。F#の関数合成が配管よりも60%遅いのはなぜですか?
配管can often be expressed as function composition and vice versa、と私は、コンパイラがそれも知っているので、私は少し実験を試みたと仮定します:FSIにこの
// simplified example of some SB helpers:
let inline bcreate() = new StringBuilder(64)
let inline bget (sb: StringBuilder) = sb.ToString()
let inline appendf fmt (sb: StringBuilder) = Printf.kbprintf (fun() -> sb) sb fmt
let inline appends (s: string) (sb: StringBuilder) = sb.Append s
let inline appendi (i: int) (sb: StringBuilder) = sb.Append i
let inline appendb (b: bool) (sb: StringBuilder) = sb.Append b
// test function for composition, putting some garbage data in SB
let compose a =
(appends "START"
>> appendb true
>> appendi 10
>> appendi a
>> appends "0x"
>> appendi 65535
>> appendi 10
>> appends "test"
>> appends "END") (bcreate())
// test function for piping, putting the same garbage data in SB
let pipe a =
bcreate()
|> appends "START"
|> appendb true
|> appendi 10
|> appendi a
|> appends "0x"
|> appendi 65535
|> appendi 10
|> appends "test"
|> appends "END"
テストを(64ビットが有効になって、上--optimize
フラグ)が与える:
> for i in 1 .. 500000 do compose 123 |> ignore;;
Real: 00:00:00.390, CPU: 00:00:00.390, GC gen0: 62, gen1: 1, gen2: 0
val it : unit =()
> for i in 1 .. 500000 do pipe 123 |> ignore;;
Real: 00:00:00.249, CPU: 00:00:00.249, GC gen0: 27, gen1: 0, gen2: 0
val it : unit =()
小さな違いは理解できますが、これは1.6(60%)のパフォーマンス低下要因です。
私は実際には多くの作業がStringBuilder
で起こることを期待していますが、明らかに合成のオーバーヘッドにはかなりの影響があります。
ほとんどの実用的な状況では、この違いはごくわずかですが、この場合のように大きな形式のテキストファイル(ログファイルなど)を書くと、影響があります。
私はF#の最新バージョンを使用しています。
ありがとう、非常に興味深い比較。サーバーGCを使用していて、通常のシングルスレッドGCがありますか?私はFSIのためにそれを設定する方法を知らない。私はコンパイルされたバージョンを比較する必要があります。私は、あなたのシステムでは、その違いが無視されるべきであることがわかっています。 – Abel
私はあなたが言及したものである '--optimize'以外の特別なフラグをFSIに使用していません。私はfsianycpu.exeも重要な場合に備えて走っています。 – Ringil
@Ringil私はラムダについて同意しない。はい、それらは最適化されていないコードで作成されます。しかし、最適化をオンにすると、9つではなく2つのラムダが表示されます。他のすべてはインライン化されます。私は、コンパイラが配管の場合よりも構成の場合にインライン展開を計算するのにもっと時間がかかることが最善でなければならないと考えています。 –