F#中懒惰的表现

Old*_*vec 3 performance f# lazy-evaluation

为什么Lazy类型的创建如此之慢?

假设以下代码:

type T() =
  let v = lazy (0.0)
  member o.a = v.Value

type T2() =
  member o.a = 0.0

#time "on"

for i in 0 .. 10000000 do
  T() |> ignore

#time "on"

for i in 0 .. 10000000 do
  T2() |> ignore
Run Code Online (Sandbox Code Playgroud)

第一个循环给了我:Real: 00:00:00.647而第二个循环给了我Real: 00:00:00.051.懒惰慢了13倍!!

我试图以这种方式优化我的代码,最终我的模拟代码速度慢了6倍.跟踪发生减速的地方很有趣...

Joh*_*mer 6

懒惰版本有一些重要的开销代码 -

 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
Run Code Online (Sandbox Code Playgroud)

将此与直接版本进行比较

 .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
Run Code Online (Sandbox Code Playgroud)

所以直接版本有一个加载然后返回,而间接版本有一个加载然后一个调用然后一个返回.

由于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";;
Run Code Online (Sandbox Code Playgroud)

这给出了以下结果:

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 = ()
Run Code Online (Sandbox Code Playgroud)

这里的lazy版本只比直接版本慢2倍,而假冒懒惰版本甚至比直接版本略快 - 这可能是由于在基准测试期间发生了GC.

  • 您可能想要添加一个关于此规模的注释,因为随机读者可能不会注意到您每次调用Lazy时都在谈论60 ns,并且只能看到13x. (2认同)