有人知道答案和/或对此有意见吗?
由于元组通常不会很大,因此我认为使用结构比使用结构更有意义。你说什么
Answers:
为了简单起见,Microsoft使所有元组类型都引用类型。
我个人认为这是一个错误。具有四个以上字段的元组是非常不寻常的,无论如何都应使用类型更丰富的替代项(例如F#中的记录类型)替换,因此只有较小的元组才有意义。我自己的基准测试显示,最多512字节的未装箱元组可能仍比装箱元组更快。
尽管内存效率是一个令人关注的问题,但我相信主要的问题是.NET垃圾收集器的开销。在.NET上,分配和收集非常昂贵,因为其垃圾收集器尚未进行非常严格的优化(例如,与JVM相比)。此外,默认的.NET GC(工作站)尚未并行化。因此,使用元组的并行程序由于所有内核争用共享垃圾收集器而停止运行,从而破坏了可伸缩性。这不仅是最主要的问题,而且AFAIK在他们研究此问题时被Microsoft完全忽略。
另一个问题是虚拟调度。引用类型支持子类型,因此,它们的成员通常是通过虚拟调度来调用的。相反,值类型不能支持子类型,因此成员调用是完全明确的,并且始终可以作为直接函数调用来执行。虚拟分派在现代硬件上非常昂贵,因为CPU无法预测程序计数器将在哪里结束。JVM竭尽全力优化虚拟调度,但.NET却没有。但是,.NET确实以值类型的形式提供了从虚拟分派的转义。因此,将元组表示为值类型可以再次显着改善此处的性能。例如,打电话GetHashCode
在2元组上,一百万次需要0.17s,但是在等效结构上调用它仅需要0.008s,即,值类型比引用类型快20倍。
通常在元组中出现这些性能问题的实际情况是使用元组作为字典中的键。我实际上是通过跟踪来自堆栈溢出问题F#的链接偶然发现此线程的,它的算法运行速度比Python慢!作者的F#程序实际上比他的Python慢,因为他使用的是盒装元组。使用手写struct
类型手动取消装箱,使他的F#程序快了好几倍,并且比Python快。如果元组以值类型而不是引用类型表示,则永远不会出现这些问题。
Tuple<_,...,_>
类型可能已经被密封,在这种情况下,尽管是引用类型,也不需要虚拟调度。我比它们为什么是引用类型更好奇为什么它们没有被密封。
原因很可能是因为只有较小的元组才有意义,因为它们的内存占用量较小。较大的元组(即具有更多属性的元组)实际上会受到性能的影响,因为它们将大于16个字节。
而不是让某些元组作为值类型,而让其他元组作为引用类型,而迫使开发人员知道哪些是元组,我可以想象微软的人认为使它们成为所有引用类型更为简单。
啊,怀疑得到证实!请参阅建立元组:
第一个主要决定是将元组视为参考还是值类型。由于只要您想更改元组的值,它们都是不可变的,因此您必须创建一个新的元组。如果它们是引用类型,则意味着如果在紧密循环中更改元组中的元素,可能会产生大量垃圾。F#元组是引用类型,但是团队认为,如果两个(也许三个)元素元组是值类型,它们可以实现性能改进。一些创建内部元组的团队使用了值而不是引用类型,因为他们的场景对创建很多托管对象非常敏感。他们发现使用值类型可以提高性能。在元组规范的初稿中,我们将二元,三元和四元元组保留为值类型,其余为引用类型。但是,在包括来自其他语言的代表的设计会议中,由于两种类型之间的语义略有不同,因此决定这种“拆分”设计会造成混淆。行为和设计的一致性被确定为比潜在性能提高更高的优先级。基于此输入,我们更改了设计,以使所有元组均为引用类型,尽管我们要求F#团队进行一些性能调查,以查看将值类型用于某些大小的元组时是否经历了加速。它有一个很好的测试方法,因为它的编译器是用F#编写的,这是一个大型程序的好例子,该程序在各种情况下都使用了元组。最后,F#团队发现,当某些元组是值类型而不是引用类型时,它并没有获得性能上的提高。这使我们对将引用类型用于元组的决定感到更好。
如果将.NET System.Tuple <...>类型定义为结构,则它们将不可伸缩。例如,长整数的三元组当前缩放如下:
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
由于元组在F#中具有内置的语法支持,并且在该语言中使用频率很高,因此“结构”元组将使F#程序员面临编写效率低下的程序而没有意识到的风险。这很容易发生:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
在我看来,“结构”元组很可能在日常编程中造成严重的低效率。另一方面,如@Jon所述,当前存在的“类”元组也导致某些效率低下。但是,我认为结构的“发生概率”乘以“潜在损坏”的乘积要比当前的类高得多。因此,当前的实现是较小的邪恶。
理想情况下,将同时具有“类”元组和“结构”元组,并且在F#中都具有语法支持!
编辑(2017-10-07)
现在完全支持结构元组,如下所示:
ref
,或者可能不喜欢所谓的“不可变结构”的事实,尤其是在装箱时。太糟糕了。.net从未实现过通过可执行文件传递参数的概念const ref
,因为在很多情况下,这种语义是真正需要的。
Dictionary
,例如:stackoverflow.com/questions/5850243 /…
对于2元组,您仍然可以始终使用早期版本的Common Type System中的KeyValuePair <TKey,TValue>。这是一个值类型。
对Matt Ellis文章的一个次要澄清是,当不变性生效时,引用类型和值类型之间的使用语义差异仅是“轻微的”(当然,这里就是这种情况)。尽管如此,我认为在BCL设计中最好不要引入使Tuple在某个阈值上交叉到引用类型的困惑。
我不知道,但是如果您曾经使用过F#元组,它就是该语言的一部分。如果我创建了一个.dll并返回了一个元组类型,那么最好将其放入其中。我怀疑现在F#是该语言的一部分(.Net 4),对CLR进行了一些修改以适应某些通用结构在F#中
来自http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records
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)
ValueTuple<...>
。请参阅C#元组类型的