【问题标题】:F# performance question: what is the compiler doing?F# 性能问题:编译器在做什么?
【发布时间】:2010-06-01 12:05:26
【问题描述】:

引用此代码:F# Static Member Type Constraints

例如,为什么

let gL = G_of 1L
[1L..100000L] |> List.map (fun n -> factorize gL n)

明显慢于

[1L..100000L] |> List.map (fun n -> factorize (G_of 1L) n)

通过查看 Reflector,我可以看到编译器以非常不同的方式处理这些中的每一个,但是对于我来说,要解读本质区别的事情太多了。我天真地认为前者会比后者表现更好,因为 gL 是预先计算的,而 G_of 1L 必须计算 100,000 次(至少看起来是这样)。

[编辑]

看起来这可能是 F# 2.0 / .NET 2.0 / 发布模式的错误,请参阅@gradbot 的回答和讨论。

【问题讨论】:

  • 有趣。这对我来说毫无意义——据我所知,生成的 IL 符合您的期望——gL 在第一种情况下是预先计算的,但其余代码几乎是等价的。也许有一些 x64 或 JIT 魔法正在发生?我在 64 位 .NET 2.0 上尝试过,得到了类似的结果(嗯,第一个是 10 秒,第二个是 7 秒)。
  • 感谢您的观看,我知道它表面上很奇怪,但我意识到静态类型约束有很多有趣的东西,可能无法按预期编译 - 有趣的命题这可能是由于 JIT “魔术”。我在 32 位 .NET 2.0 上得到了相同的数字。
  • 我的猜测是它与第一个版本是闭包和第二个版本只是一个匿名函数有关。 F# 必须为闭包创建某种匿名对象。
  • 有趣的建议 gradbot,反编译的 Reflector 代码看起来确实不一样。但是该理论是否与 List.map factorizeL 和 List.map factorizeG (在链接中引用代码)执行与此处示例相同的事实一致(因此两者都将使用命名函数 - 即使我们给出了完整的定义factorizeL 不使用部分应用程序(不确定这是否会有所不同,但在这里似乎没有)?

标签: f# compilation


【解决方案1】:

Reflector 显示 test2() 变成了 4 个类,而 test1() 变成了两个类。这仅在调试模式下发生。反射器在发布模式下显示相同的代码(每个类一个)。不幸的是,当我尝试在 C# 中查看源代码并且 IL 真的很长时,Reflector 崩溃了。

let test1() =
    let gL = G_of 1L
    [1L..1000000L] |> List.map (fun n -> factorize gL n)

let test2() =
    [1L..1000000L] |> List.map (fun n -> factorize (G_of 1L) n)

快速基准测试。

let sw = Stopwatch.StartNew()
test1() |> ignore
sw.Stop()
Console.WriteLine("test1 {0}ms", sw.ElapsedMilliseconds)

let sw2 = Stopwatch.StartNew()
test2() |> ignore
sw2.Stop()
Console.WriteLine("test2 {0}ms", sw2.ElapsedMilliseconds)

在 I7 950 @3368Mhz、windows 7 64bit、VS2010 F#2.0 上运行基准测试

x86 调试
test1 8216ms
test2 8237ms

x86 发布
test1 6654ms
test2 6680ms

x64 调试
test1 10304ms
test2 10348ms

x64 版本
test1 8858ms
test2 8977ms

这是完整的代码。

open System
open System.Diagnostics

let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a>
let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a>
let inline two_of (target:'a) : 'a = one_of(target) + one_of(target)
let inline three_of (target:'a) : 'a = two_of(target) + one_of(target)
let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target)

let inline any_of (target:'a) (x:int) : 'a =
    let one:'a = one_of target
    let zero:'a = zero_of target
    let xu = if x > 0 then 1 else -1
    let gu:'a = if x > 0 then one else zero-one

    let rec get i g = 
        if i = x then g
        else get (i+xu) (g+gu)
    get 0 zero 

type G<'a> = {
    negone:'a
    zero:'a
    one:'a
    two:'a
    three:'a
    any: int -> 'a
}    

let inline G_of (target:'a) : (G<'a>) = {
    zero = zero_of target
    one = one_of target
    two = two_of target
    three = three_of target
    negone = negone_of target
    any = any_of target
}

let inline factorizeG n = 
    let g = G_of n
    let rec factorize n j flist =  
        if n = g.one then flist 
        elif n % j = g.zero then factorize (n/j) j (j::flist) 
        else factorize n (j + g.one) (flist) 
    factorize n g.two []

let inline factorize (g:G<'a>) n =   //'
    let rec factorize n j flist =  
        if n = g.one then flist 
        elif n % j = g.zero then factorize (n/j) j (j::flist) 
        else factorize n (j + g.one) (flist) 
    factorize n g.two []

let test1() =
    let gL = G_of 1L
    [1L..100000L] |> List.map (fun n -> factorize gL n)

let test2() =
    [1L..100000L] |> List.map (fun n -> factorize (G_of 1L) n)

let sw2 = Stopwatch.StartNew()
test1() |> ignore
sw2.Stop()
Console.WriteLine("test1 {0}ms", sw2.ElapsedMilliseconds)

let sw = Stopwatch.StartNew()
test2() |> ignore
sw.Stop()
Console.WriteLine("test2 {0}ms", sw.ElapsedMilliseconds)

Console.ReadLine() |> ignore

【讨论】:

  • 在问题中,test1() 较慢,而不是 test2()。
  • @Yin:没错。 @gradbot:基于调试和发布模式的不同编译的非常有趣的观察......你是否观察到任何取决于模式的性能差异?
  • 我在 Reflector 中遇到了同样的崩溃。
  • 谢谢!我很惊讶即使在调试模式下你的 test1 和 test2 结果非常接近,而我自己和 Tomas 都看到 test2 比 test1 慢了大约 3 秒(我很确定我处于调试模式,我不确定关于托马斯)。但是对我来说已经很晚了,我明天必须在发布模式下尝试一下。
  • 很好奇。在 F# 2.0 / .NET 2.0 / x86 / Debug-mode 下,我能够复制与您类似的结果(test1 稍微快一点,正如我最初预期的那样),但在 Release 模式下 test1 运行速度要慢得多。看起来像是特定于 .NET 2.0 发布模式的错误。 (注意,我最初的基准测试是通过 FSI 完成的,它显然总是在发布模式下编译,至少在默认情况下,而且运行速度也比在 .exe 中编译和运行时更快)。
猜你喜欢
  • 1970-01-01
  • 2019-03-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-11
  • 2019-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多