Performance 为什么.Net 4.0中的新元组类型是引用类型(类)而不是值类型(结构)

Performance 为什么.Net 4.0中的新元组类型是引用类型(类)而不是值类型(结构),performance,class,struct,.net-4.0,Performance,Class,Struct,.net 4.0,有人知道答案和/或对此有看法吗 由于元组通常不会很大,因此我认为对这些元组使用结构比使用类更有意义。你怎么看?原因很可能是因为只有较小的元组才有意义作为值类型,因为它们的内存占用很小。较大的元组(即具有更多属性的元组)实际上会影响性能,因为它们会大于16字节 与其让一些元组成为值类型,而让其他元组成为引用类型,还不如强迫开发人员知道哪些是引用类型,我可以想象,微软的员工认为让它们成为所有引用类型更简单 啊,怀疑得到证实!请参阅: 第一个重大决定是 将元组视为引用 或值类型。既然是 任何时候你想要

有人知道答案和/或对此有看法吗


由于元组通常不会很大,因此我认为对这些元组使用结构比使用类更有意义。你怎么看?

原因很可能是因为只有较小的元组才有意义作为值类型,因为它们的内存占用很小。较大的元组(即具有更多属性的元组)实际上会影响性能,因为它们会大于16字节

与其让一些元组成为值类型,而让其他元组成为引用类型,还不如强迫开发人员知道哪些是引用类型,我可以想象,微软的员工认为让它们成为所有引用类型更简单

啊,怀疑得到证实!请参阅:

第一个重大决定是 将元组视为引用 或值类型。既然是 任何时候你想要改变都是不可变的 元组的值,您必须 创建一个新的。如果是 引用类型,这意味着可以 如果你这样做,会产生大量垃圾吗 正在更改中的元组中的元素 紧密循环。F#元组是引用 类型,但有一种来自 团队认为他们可以实现 性能改进(如果有两个),以及 可能有三个元素元组 而不是值类型。有些球队 创建了内部元组,并使用了 值而不是引用类型, 因为他们的场景非常复杂 对创建大量托管数据非常敏感 物体。他们发现使用一个值 类型给了他们更好的性能。在里面 我们的元组初稿 规格,我们保留了两个-, 三元元组和四元元组 值类型,其余为 引用类型。但是, 设计会议,包括 其他语文的代表 决定这一“分裂” 设计会令人困惑,因为 两种语言的语义略有不同 这两种类型。行为一致性 设计被确定为 优先权高于潜力 性能提高。基于此, 输入后,我们更改了设计,以便 所有元组都是引用类型, 尽管我们要求F#团队 要查看的一些性能调查 如果在使用时遇到加速 某些大小的元组的值类型。 这是一个很好的测试方法,因为 它的编译器是用F#编写的,是一个 一个大型程序的好例子 在各种场景中使用元组。 最后,F#团队发现 没有得到性能改进 当某些元组是值类型时 而不是引用类型。这使得 让我们对我们的决定感觉更好 对元组使用引用类型


对于2元组,您仍然可以始终使用公共类型系统早期版本中的KeyValuePair。它是一种值类型


马特·埃利斯(Matt Ellis)文章的一个次要澄清是,当不变性生效时,引用类型和值类型之间的使用语义差异只是“微小的”(当然,这里就是这种情况)。尽管如此,我认为在BCL设计中最好不要引入将元组交叉到某个阈值处的引用类型的混淆。

我不知道,但如果您曾经使用过F,元组是语言的一部分。如果我创建了一个.dll并返回了一种元组类型,那么最好有一种类型将其放入其中。现在我怀疑F#是语言(.Net 4)的一部分,对CLR进行了一些修改,以适应F中的一些常见结构#


为了简单起见,微软制作了所有元组类型引用类型

我个人认为这是一个错误。超过4个字段的元组是非常不寻常的,无论如何都应该用更类型的替代品(例如F#中的记录类型)来替换,因此只有小元组才有实际意义。我自己的基准测试表明,最大512字节的未装箱元组仍然比装箱元组快

尽管内存效率是一个值得关注的问题,但我认为主要问题是.NET垃圾收集器的开销。NET上的分配和收集非常昂贵,因为它的垃圾收集器没有经过非常严格的优化(例如,与JVM相比)。此外,默认的.NETGC(工作站)尚未并行化。因此,当所有内核争夺共享垃圾收集器时,使用元组的并行程序会陷入停顿,破坏可伸缩性。这不仅是最主要的问题,而且在微软研究这个问题时,他们完全忽略了这一点

另一个问题是虚拟调度。引用类型支持子类型,因此,它们的成员通常通过虚拟分派来调用。相反,值类型不能支持子类型,因此成员调用是完全明确的,并且始终可以作为直接函数调用执行。虚拟调度在现代硬件上非常昂贵,因为CPU无法预测程序计数器的最终位置。JVM竭尽全力优化虚拟调度,但.NET没有。但是,.NET确实以值类型的形式提供了从虚拟分派的转义。因此,将元组表示为值类型同样可以显著提高性能。例如,在2元组上调用
GetHashCode
一百万次需要0.17s,但在等效结构上调用它只需要0.008s,即值类型比引用类型快20倍

元组的这些性能问题通常出现的实际情况是在字典中将元组用作键。实际上,我是通过跟踪堆栈溢出问题中的一个链接偶然发现了这条线索的,作者的F#程序比他的Python慢,正是因为他使用的是装箱元组。使用手工编写的
struct
类型手动解装箱可以使他的F#程序快几倍,而且速度更快
let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;

val scalarMultiply : float -> float * float * float -> float * float * float

scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3