3

F# が次のコードを最適化するほど賢くない理由はありますか? fast = 880slow = 8090

type Data = { fn: int * int -> int }
let fn (x, y) = x + y
let data = { fn = fn }

let mutable a = 0
let s = System.Diagnostics.Stopwatch()

s.Start()
for i in 0 .. 1000000000 do
  a <- fn(i, i)
printfn "fast = %d" s.ElapsedMilliseconds

s.Restart()
for i in 0 .. 1000000000 do
  a <- data.fn(i, i)
printfn "slow = %d" s.ElapsedMilliseconds
4

2 に答える 2

7

F# が次のコードを最適化するほど賢くない理由はありますか?

F# コンパイラがこのケースを最適化できるとしたら、私は驚くでしょう。最後に、fn機能を実行するためではなく、データを保持するためのレコードフィールドです。

非静的メンバーであっても、これらのメンバーは環境の変化によって制限されるため、コンパイラはそれらをインライン化できません。バインディングを宣言することによりlet、静的環境の利点が得られ、コンパイラはいくつかの単純なケースでインライン化できます。

実際、この例ではfn関数がインライン化されています (追加inlineしても実行時間は変わりません)。遅い例は、より強力な構築物を手元に置くために支払う代償です。

関数のレコードを作成する必要があるときはいつでも、インターフェイスとオブジェクト式がより良い代替手段であることを覚えておいてください (オーバーヘッドが少なく、インテリセンスが優れています)。

type Data2 =
    abstract fn : int * int -> int

let data2 = 
    { new Data2 with
        member __.fn (x, y) = fn (x, y) }

s.Restart()
for i in 0 .. 1000000000 do
  a <- data2.fn(i, i)
printfn "a bit slow = %d" s.ElapsedMilliseconds

これは、F# Interactive 64 ビットで実行して得た結果です。

fast = 614
slow = 7498
a bit slow = 2765

したがって、インターフェースベースのアプローチは、レコードベースのアプローチよりも 3 倍速く、インライン メソッドよりも 3 倍遅くなります。

于 2013-04-30T18:46:53.313 に答える
4

高速パスはインライン化fnされていますが、低速パスは関数呼び出しを行っています。

レコードは必要ないことに注意してください。次のことを行うだけで十分です。

let fn' = fn
for i in 0 .. 1000000000 do
  a <- fn'(i, i)
printfn "slow = %d" s.ElapsedMilliseconds
于 2013-04-30T17:40:23.323 に答える