レイジーバージョンにはいくつかの重要なオーバーヘッドコードがあります-
60 .method public specialname
61 instance default float64 get_a () cil managed
62 {
63 // Method begins at RVA 0x2078
64 // Code size 14 (0xe)
65 .maxstack 3
66 IL_0000: ldarg.0
67 IL_0001: ldfld class [FSharp.Core]System.Lazy`1<float64> Test/T::v
68 IL_0006: tail.
69 IL_0008: call instance !0 class [FSharp.Core]System.Lazy`1<float64>::get_Value()
70 IL_000d: ret
71 } // end of method T::get_a
これを直接バージョンと比較してください
.method public specialname
130 instance default float64 get_a () cil managed
131 {
132 // Method begins at RVA 0x20cc
133 // Code size 10 (0xa)
134 .maxstack 3
135 IL_0000: ldc.r8 0.
136 IL_0009: ret
137 } // end of method T2::get_a
したがって、直接バージョンにはロードとリターンがあり、間接バージョンにはロードとコールとリターンがあります。
lazy
バージョンには余分な呼び出しがあるので、かなり遅くなると思います。
更新:
それで、メソッド呼び出しを必要としないカスタムバージョンを作成できるかどうか疑問に思いましたlazy
-オブジェクトを作成するだけでなく、実際にメソッドを呼び出すようにテストも更新しました。コードは次のとおりです。
type T() =
let v = lazy (0.0)
member o.a() = v.Value
type T2() =
member o.a() = 0.0
type T3() =
let mutable calculated = true
let mutable value = 0.0
member o.a() = if calculated then value else failwith "not done";;
#time "on"
let lazy_ =
for i in 0 .. 1000000 do
T().a() |> ignore
printfn "lazy"
#time "on"
let fakelazy =
for i in 0 .. 1000000 do
T3().a() |> ignore
printfn "fake lazy"
#time "on"
let direct =
for i in 0 .. 1000000 do
T2().a() |> ignore
printfn "direct";;
これにより、次の結果が得られます。
lazy
Real: 00:00:03.786, CPU: 00:00:06.443, GC gen0: 7
val lazy_ : unit = ()
--> Timing now on
fake lazy
Real: 00:00:01.627, CPU: 00:00:02.858, GC gen0: 2
val fakelazy : unit = ()
--> Timing now on
direct
Real: 00:00:01.759, CPU: 00:00:02.935, GC gen0: 2
val direct : unit = ()
ここで、lazy
バージョンは直接バージョンより2倍遅く、偽のレイジーバージョンは直接バージョンよりもわずかに高速です。これはおそらくベンチマーク中に発生したGCが原因です。