我已将 Ocaml 程序转换为 F#,整体性能与 Ocaml 相同。
但是,为了达到这一点,我尝试用 Option 值替换例外。
该程序适用于 list , maps 等具有int*int*int
(=三元组ints
)的程序。
我有一个性能泄漏,我不明白。任何人都可以解释我的能力 在一个名为
的函数中有90%的总执行时间System.Tuple`3.System.Collections.IStructuralEquatable.Equals(
object, class System.Collections.IEqualityComparer)
我能做些什么呢?
答案 0 :(得分:3)
正如人们已经注意到很难诊断出没有代码的问题,但我会尽我所能: - )
你的问题使我运行了一个我一直计划运行一段时间的测试,这是测试引用类型与值类型作为关联容器的键的性能。基于对.net运行时的模糊感觉,我的假设是,对于小型密钥大小(您的3个整数),值类型将胜出。我似乎错了([编辑]实际上进一步测试证明它是正确的![/ edit])
让我们看一些代码(按照惯例,微观性能测试,所以带上一粒盐):
我使用了5种不同风格的不同容器来存储整数(F#非常适合为结构类型和记录创建相等和比较器):
type KeyStruct(_1':int, _2':int, _3':int) = struct
member this._1 = _1'
member this._2 = _2'
member this._3 = _3'
end
type KeyGenericStruct<'a>(_1':'a, _2':'a, _3':'a) = struct
member this._1 = _1'
member this._2 = _2'
member this._3 = _3'
end
type KeyRecord = { _1 : int; _2 : int; _3 : int }
type KeyGenericRecord<'a> = { _1 : 'a; _2 : 'a; _3 : 'a }
加上我使用了原始元组(int * int * int)
然后我创建了以下测试工具:
let inline RunTest<'a when 'a : equality> iterationCount createAssociativeMap (createKey:_->_->_->'a) =
System.GC.Collect ()
System.GC.WaitForFullGCComplete () |> ignore
let data = [|
for a in 0..99 do
for b in 0..99 do
for c in 0..99 do
yield a,b,c |]
// shuffle
let r = System.Random (0)
for i = 0 to data.Length-1 do
let j = r.Next (i, data.Length)
let t = data.[i]
data.[i] <- data.[j]
data.[j] <- t
let keyValues =
data
|> Array.mapi (fun i k -> k, 0.5-(float i)/(float data.Length))
|> Array.toSeq
let sw = System.Diagnostics.Stopwatch.StartNew ()
let mapper = createAssociativeMap createKey keyValues
let creationTime = sw.ElapsedMilliseconds
let sw = System.Diagnostics.Stopwatch.StartNew ()
let mutable checksum = 0.
for i = 0 to iterationCount do
let a, b, c = r.Next 100, r.Next 100, r.Next 100
let key = createKey a b c
checksum <- checksum + (mapper key)
let accessTime= sw.ElapsedMilliseconds
printfn "checksum %f elapsed %d/%d (%s)" checksum creationTime accessTime (typeof<'a>.Name)
let RunNTrials<'a when 'a : equality> = RunTest<'a> 1000000
然后使用一些不同类型的关联容器运行它:
let createDictionary create keyValues =
let d = System.Collections.Generic.Dictionary<_,_> ()
keyValues
|> Seq.map (fun ((_1,_2,_3),value) -> create _1 _2 _3, value)
|> Seq.iter (fun (key,value) -> d.[key] <- value)
(fun key -> d.[key])
let createDict create keyValues =
let d =
keyValues
|> Seq.map (fun ((_1,_2,_3),value) -> create _1 _2 _3, value)
|> dict
(fun key -> d.[key])
let createMap create keyValues =
let d =
keyValues
|> Seq.map (fun ((_1,_2,_3),value) -> create _1 _2 _3, value)
|> Map.ofSeq
(fun key -> d.[key])
let createCustomArray create keyValues =
let maxA = 1 + (keyValues |> Seq.map (fun ((a,_,_),_) -> a) |> Seq.max)
let maxB = 1 + (keyValues |> Seq.map (fun ((_,b,_),_) -> b) |> Seq.max)
let maxC = 1 + (keyValues |> Seq.map (fun ((_,_,c),_) -> c) |> Seq.max)
let createIndex a b c = a * maxB * maxC + b * maxC + c
let values : array<float> = Array.create (maxA * maxB * maxC) 0.
keyValues
|> Seq.iter (fun ((a,b,c),d) -> values.[createIndex a b c] <- d)
(fun (a,b,c) -> values.[a * maxB * maxC + b * maxC + c])
let RunDictionary<'a when 'a : equality> = RunNTrials<'a> createDictionary
let RunDict<'a when 'a : equality> = RunNTrials<'a> createDict
let RunMap<'a when 'a : comparison> = RunNTrials<'a> createMap
let RunCustomArray = RunNTrials<_> createCustomArray
按以下方式运行测试:
printfn "Using .net's System.Collections.Generic.Dictionary"
RunDictionary (fun a b c -> { KeyRecord._1=a; _2=b; _3=c })
RunDictionary (fun a b c -> { KeyGenericRecord._1=a; _2=b; _3=c })
RunDictionary (fun a b c -> KeyStruct(a, b, c))
RunDictionary (fun a b c -> KeyGenericStruct(a, b, c))
RunDictionary (fun a b c -> (a, b, c))
printfn "Using f# 'dict'"
RunDict (fun a b c -> { KeyRecord._1=a; _2=b; _3=c })
RunDict (fun a b c -> { KeyGenericRecord._1=a; _2=b; _3=c })
RunDict (fun a b c -> KeyStruct(a, b, c))
RunDict (fun a b c -> KeyGenericStruct(a, b, c))
RunDict (fun a b c -> (a, b, c))
printfn "Using f# 'Map'"
RunMap (fun a b c -> { KeyRecord._1=a; _2=b; _3=c })
RunMap (fun a b c -> { KeyGenericRecord._1=a; _2=b; _3=c })
RunMap (fun a b c -> KeyStruct(a, b, c))
RunMap (fun a b c -> KeyGenericStruct(a, b, c))
RunMap (fun a b c -> (a, b, c))
printfn "Using custom array"
RunCustomArray (fun a b c -> (a, b, c))
得到了以下结果(校验和只是为了确保我没有做任何太愚蠢的事情)“已用过的n / m”是“已过去{容器创建时间} / {容器访问时间}”:< / p>
Using .net's System.Collections.Generic.Dictionary
checksum -55.339450 elapsed 874/562 (KeyRecord)
checksum -55.339450 elapsed 1251/898 (KeyGenericRecord`1)
checksum -55.339450 elapsed 569/1024 (KeyStruct)
checksum -55.339450 elapsed 740/1427 (KeyGenericStruct`1)
checksum -55.339450 elapsed 2497/2218 (Tuple`3)
Using f# 'dict'
checksum -55.339450 elapsed 979/628 (KeyRecord)
checksum -55.339450 elapsed 1614/1206 (KeyGenericRecord`1)
checksum -55.339450 elapsed 3237/5625 (KeyStruct)
checksum -55.339450 elapsed 3290/5626 (KeyGenericStruct`1)
checksum -55.339450 elapsed 2448/1914 (Tuple`3)
Using f# 'Map'
checksum -55.339450 elapsed 8453/2638 (KeyRecord)
checksum -55.339450 elapsed 31301/25441 (KeyGenericRecord`1)
checksum -55.339450 elapsed 30956/26931 (KeyStruct)
checksum -55.339450 elapsed 53699/49274 (KeyGenericStruct`1)
checksum -55.339450 elapsed 32203/25274 (Tuple`3)
Using custom array
checksum -55.339450 elapsed 484/160 (Tuple`3)
多次运行显示数量变化很小,但主要趋势确实存在。因此,主要是我们测试是否发生拳击(在map和dict中),然后是GetHashCode()实现(通用版本,如果比完全类型版本慢;而Tuple最糟糕的话),并且在Map中映射CompareTo。
现在这会留下你的问题?好吧,如果所有的时间都花在了元组的等号上,那么改为记录类型可能会有所帮助!
但可能不是:-) [因为如果它是一个哈希容器,那么GetHashCode不应该导致很多冲突,它就是一个地图然后它会是CompareTo)
无论如何,在实际代码中你显然会有不同的垃圾收集器影响等等,正如我所说的那样把它当作它...
我做了一些进一步的测试,包括在Tasks中多次开始每个测试,并且每个测试都反复并行(开始比我的核心更多的任务),然后花费平均时间来完成。
这样做的原因是考虑垃圾收集时间。
当我这样做时,非通用结构键的原始假设确实获胜。
如果有人真的感兴趣并且不愿意做出更改,我可以发布代码,但实际上我认为我是唯一一个阅读此代码的人,所以这更多只是我的个人笔记: - )