繁体   English   中英

F#中懒惰的表现

[英]Performance of Lazy in F#

为什么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

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

我试图以这种方式优化我的代码,最终我的模拟代码速度慢了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

将此与直接版本进行比较

 .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。

更新.net核心世界

Lazy中添加了一个新的构造函数来处理常量,例如你的情况。 不幸的是,F#的lazy “伪关键字”总是(目前!)将常量包装为函数。

无论如何,如果你改变:

let v = lazy (0.0)

至:

let v = Lazy<_> 0.0 // NB. Only .net core at the moment

然后你会发现你的T()类只需要你T2 ~3倍。

(具有惰性常量的重点是什么?这意味着当你有混合的常量和真正的懒惰项时,你可以使用Lazy作为抽象,而且开销很小......)

...和...

如果您实际使用创建的值多次,那么开销会进一步缩小。 即,例如:

open System.Diagnostics

type T() =
  let v = Lazy<_> 0.1
  member o.a () = v.Value

type T2() =
  member o.a () = 0.1

let withLazyType () =
    let mutable sum = 0.0
    for i in 0 .. 10000000 do
        let t = T()
        for __ = 1 to 10 do
            sum <- sum + t.a()
    sum

let withoutLazyType () =
    let mutable sum = 0.0
    for i in 0 .. 10000000 do
        let t = T2()
        for __ = 1 to 10 do
            sum <- sum + t.a()
    sum

let runtest name count f =
    let mutable checksum = 0.
    let mutable totaltime = 0L
    for i = 0 to count do
        if i = 0 then
            f () |> ignore // warm up
        else
            let sw = Stopwatch.StartNew ()
            checksum <- checksum + f ()
            totaltime <- totaltime + sw.ElapsedMilliseconds
    printfn "%s: %4d (checksum=%f for %d runs)" name (totaltime/int64 count) checksum count

[<EntryPoint>]
let main _ =
    runtest "w/o  lazy" 10 withoutLazyType
    runtest "with lazy" 10 withLazyType

    0

带来时间上的差异<2次。

NB。 我参与了新的懒惰实现 ......

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM