任何不使记录成为值类型的原因

  • 本文关键字:类型 记录 任何不 f#
  • 更新时间 :
  • 英文 :


所以我一直在试验这样的代码:

[<Struct>]
type Component = {
Num : int
}
with 
static member New = {
Num = 0
}
let init n = 
seq {
for _ in 0..n do 
yield Component.New
}
|> Seq.toArray
let test (c : Component []) = 
c
|> Array.map ( fun c -> { c with Num = c.Num + 1} )
[<EntryPoint>]
let main argv =
let sw = Stopwatch()
let c = init 10000000
sw.Start ()
let c' = test c
sw.Stop ()
printfn "%A" sw.ElapsedMilliseconds
0

我在运行这个测试时得到了这些基准测试。

Reference type and list: 1450
Reference type and array: 850
Value type and list: 700
Value type and array: 80

数组会比列表更快这一事实显然在预期范围内,我也预期值类型会更快,但不会快那么多。然而,我想知道,是否存在不希望记录为值类型的情况?它似乎总是更可取?

对于这样的小结构,尤其是在数组中保存时,没有理由因为性能原因而不将其作为结构。即使结构大于16字节(这是一般的指导原则(,在这种情况下,它通常也会产生更快的性能:

  • 分配到阵列中
  • 该数组的直接处理
  • 不再处理该数组

如果后续的几个数组处理例程也能产生更好的性能,我不会感到惊讶,因为将一堆值类型分配到数组中意味着它通常都会加载到CPU缓存行中,并且处理速度快得离谱。

然而,当你没有一直使用值类型(数组、跨度(,或者如果你改变了做事的方式,事情会变得更有趣:

  • 基于阵列的操作会创建新的阵列,最终数据的连续重新创建会压倒数据的实际处理
  • 有时您使用F#列表或序列或其他引用类型,其中数据不会全部加载到CPU缓存行中
  • 有时,您会混合和匹配值类型和引用类型,将内容存储在值类型中的额外复制会对性能产生负面影响

简而言之,这种特殊的场景非常适合使用值类型——包含的数据都只是一个基元值类型,它的实例存储在一个数组中,每次都会重新创建相同的结构。因此,如果性能非常重要,那么应该在这种情况下使用值类型。

将代码更改为使用列表,而是预先分配数据,而不是重新创建结构,只需在基准测试下返回一个单一值的列表,如下所示:

open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Running
module ReferenceType =
type Component = {
Num0 : int
}
with 
static member New = {
Num0 = 0
}
let init n = 
seq {
for _ in 0..n do 
yield Component.New
}
|> Seq.toList
let test cs = 
cs
|> List.map (fun c -> c.Num0 + 1)
module ValueType = 
[<Struct>]
type Component = {
Num0 : int
}
with 
static member New = {
Num0 = 0
}
let init n = 
seq {
for _ in 0..n do 
yield Component.New
}
|> Seq.toList
let test cs = 
cs
|> List.map (fun c -> c.Num0 + 1)
[<MemoryDiagnoser>]
type ReferenceVsValueType() =
let refs = ReferenceType.init 10_000
let vals = ValueType.init 10_000
[<Benchmark(Baseline=true)>]
member _.BuiltIn() = ReferenceType.test refs
[<Benchmark>]
member _.ValueType() = ValueType.test vals
[<EntryPoint>]
let main argv =
BenchmarkRunner.Run<ReferenceVsValueType>() |> ignore
0 // return an integer exit code

你会得到非常非常接近的结果:

tdDev>
方法平均值错误Gen 1第2代已分配
内置90.99μs-312.53 KB
值类型87.13μs1.712μs-312.53KB

最新更新