Optimization 是什么让非优化F#代码比优化代码更快?
所以我决定尝试一下F,并将我用C写的算法移植到F。在某一点上,我注意到调试版本比发布版本运行得更快。然后我玩了优化设置,得到了以下结果: 时间显示了超过100000次运行的算法的总执行时间。我正在使用VisualStudio2010SP1附带的F#编译器。目标平台是任何CPUOptimization 是什么让非优化F#代码比优化代码更快?,optimization,compiler-construction,f#,Optimization,Compiler Construction,F#,所以我决定尝试一下F,并将我用C写的算法移植到F。在某一点上,我注意到调试版本比发布版本运行得更快。然后我玩了优化设置,得到了以下结果: 时间显示了超过100000次运行的算法的总执行时间。我正在使用VisualStudio2010SP1附带的F#编译器。目标平台是任何CPU Opt off, tail calls off: 5.81s Opt off, tail calls on : 5.79s Opt on , tail calls off: 6.48s Opt on , tail call
Opt off, tail calls off: 5.81s
Opt off, tail calls on : 5.79s
Opt on , tail calls off: 6.48s
Opt on , tail calls on : 6.40s
我真的很困惑——为什么优化会让代码运行得更慢?该算法的C#版本没有表现出这种行为(尽管它的实现方式略有不同)
这是F#代码的精简版本,它是一种在分子中发现模式的算法。这个F#程序所依赖的所有代码都是用F#编写的
优化关闭时,我得到约7.5秒;优化打开时,我得到约8.0秒。静止目标=任何CPU(我有i7-860处理器)
EDIT3:
在我发布上一次编辑后,我想我应该只在列表上尝试
所以
let stopWatch = System.Diagnostics.Stopwatch.StartNew()
let runs = 1000000
let result1 =
seq {
for i in 1 .. runs do
let list = [ 1 .. i % 100 + 5 ]
yield list
} |> Seq.nth (runs - 1)
stopWatch.Stop()
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds
printfn "%A" result1
我用opt获得3分左右。关闭,使用opt约3.5秒。开
EDIT4:
如果我删除seq builder并执行
let stopWatch = System.Diagnostics.Stopwatch.StartNew()
let runs = 1000000
let mutable ret : int list = []
for i in 1 .. runs do
let list = [ 1 .. i % 100 + 5 ]
ret <- list
stopWatch1.Stop()
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds
printfn "%A" ret
而该代码在~1.7秒时的运行速度几乎是F#解的两倍
EDIT5:
根据与Jon Harrop的讨论,我发现了导致优化代码运行速度变慢的原因(我仍然不知道为什么)
如果我从
member this.Atoms : IAtom seq =
seq {
for r in this.ResidueSet do yield! r.Atoms
yield! this.AtomSet
}
到
然后程序在优化和非优化版本中运行~7.1s。这比seq
版本慢,但至少是一致的
因此,似乎F#编译器无法优化计算表达式,实际上通过尝试这样做会使它们变慢。我还可以观察到您的包装器代码和倒数第二个示例在启用优化的情况下运行得稍慢,但差异小于10%,尽管异常,优化有时会略微降低性能,对此我并不感到惊讶 我应该注意到,您的编码风格为优化留下了很大的空间,但是如果没有完整的源代码,我就不可能帮助优化它。您的示例使用以下代码:
let result1 =
seq {
for i in 1 .. runs do
let list = [ 1 .. i % 100 + 5 ]
yield list
} |> Seq.nth (runs - 1)
当这一过程更短、更惯用且快几个数量级时:
let result1 =
Seq.init runs (fun i -> List.init ((i+1) % 100 + 5) ((+) 1))
|> Seq.nth (runs - 1)
编辑
在下面的注释中,您表示希望执行函数参数,在这种情况下,我不会假设Seq.nth
将为您执行此操作,因此我将使用for
循环:
let mutable list = []
for i=1 to runs do
list <- List.init (i % 100 + 5) ((+) 1)
list
let可变列表=[]
对于i=1的运行
这个列表看起来就像编译器在优化特定算法时失败了一样。@Niklas:嗯,是的。但这肯定是有原因的,我想知道,没有工作代码,什么都说不出来。您的residence
和IAtom
类型未定义。您的结构
类型定义似乎不是有效的F#代码。你能提供一个工作示例吗?@JonHarrop上传了一个(希望是)工作解决方案来澄清IAtom etc类型-这些类型来自我正在处理的库。IStructure
,AtomCollection
,BondCollection
和KDAtomTree
都没有定义…我了解编码风格,我对F#相当陌生。我只是很惊讶优化器使代码变得像示例中那样简单,实际上比运行速度慢,至少与未优化的版本一样快。而且似乎Seq.init f |>Seq.nth I
只实际执行函数f
一次。这并不是我真正想要的行为…作为我上一个例子。注释:让t=ref 0;设x=Seq.init1000(乐趣i->t:=!t+i;!t)|>Seq.nth999;printfn“%A%A”!t x
打印999。我遗漏了什么吗?好吧,我知道你可以优化这个特殊的语句。但我想我们这里有点不对劲。我最初的问题是为什么优化器会使代码运行变慢。是的。我不知道为什么F#优化器在这种情况下会稍微降低性能。我确实知道seq的F#实现显然是次优的,而C#实现效率更高。正如我所说的,优化有时会略微降低性能,这并不奇怪。如果您将整个源代码编译为Debug或Release(而不是仅仅将包装器和DLL编译为Release),您会得到什么结果?
let result1 =
seq {
for i in 1 .. runs do
let list = [ 1 .. i % 100 + 5 ]
yield list
} |> Seq.nth (runs - 1)
let result1 =
Seq.init runs (fun i -> List.init ((i+1) % 100 + 5) ((+) 1))
|> Seq.nth (runs - 1)
let mutable list = []
for i=1 to runs do
list <- List.init (i % 100 + 5) ((+) 1)
list