值类型或引用类型的好处?
Posted
技术标签:
【中文标题】值类型或引用类型的好处?【英文标题】:Benefit of Value Types over Reference Types? 【发布时间】:2012-03-09 20:26:30 【问题描述】:看到每次作为参数传递时都会创建新的值类型实例,我开始考虑使用ref
或out
关键字可以显着提高性能的场景。
一段时间后,我突然意识到,虽然我看到使用值类型的不足之处,但我不知道有什么好处。 所以我的问题相当直截了当 - 拥有值类型的目的是什么?通过复制结构而不是仅仅创建对它的新引用,我们可以获得什么?
在我看来,只有像 Java 这样的引用类型会容易得多。
编辑:为了澄清这一点,我不是指小于 8 字节(引用的最大大小)的值类型,而是指 8 字节或更多字节的值类型。 p>
例如 - 包含四个 int
值的 Rectangle
结构。
【问题讨论】:
Java真的只有引用类型吗? 一百万字节的数组如果是值类型会占用多少字节?如果它们是引用类型,它会占用多少? 是的。这实际上有时会成为一个问题。甚至 DateTime 也是一个对象。 @usr: "即使 DateTime 也是一个对象" - 不知道你想说什么 @dtryon:在Java中,所有用户定义的类型都是引用类型,但原始类型(如int
)不是。
【参考方案1】:
像Rectangle
这样的值类型的一个主要优点是,如果一个有n 个Rectangle
类型的存储位置,可以确定一个有n 个不同的Rectangle
类型的实例。如果一个数组MyArray
类型为Rectangle
,长度至少为2,则类似MyArray[0] = MyArray[1]
的语句会将MyArray[1]
的字段复制到MyArray[0]
的字段中,但它们将继续引用不同的@ 987654329@ 个实例。如果然后执行一条语句行MyArray[0].X += 4
,它将修改一个实例的字段X
,而不修改任何其他数组槽或Rectangle
实例的X
值。顺便提一下,创建数组会立即用可写的Rectangle
实例填充它。
想象一下,如果Rectangle
是一个可变类类型。创建一个可变的Rectangle
实例数组需要一个第一维数组,然后为数组中的每个元素分配一个新的Rectangle
实例。如果想将一个矩形实例的值复制到另一个矩形实例,则必须说类似MyArray[0].CopyValuesFrom(MyArray[1])
[当然,如果MyArray[0]
没有填充对新实例的引用,则会失败)。如果一个人不小心说MyArray[0] = MyArray[1]
,那么写给MyArray[0].X
也会影响MyArray[1].X
。讨厌的东西。
请务必注意,在 C# 和 vb.net 中的一些地方,编译器会隐式复制值类型,然后对副本进行操作,就好像它是原始的一样。这是一个非常不幸的语言设计,并促使一些人提出值类型应该是不可变的命题(因为大多数涉及隐式复制的情况只会导致可变值类型的问题)。当编译器非常不善于警告语义上可疑的副本会产生破坏行为的情况时,这样的概念可能是合理的。不过,它现在应该被认为是过时的,因为任何体面的现代编译器都会在隐式复制会产生破坏语义的大多数情况下标记错误,包括所有结构仅通过构造函数、属性设置器或对公共可变字段的外部赋值进行变异的情况.像MyArray[0].X += 5
这样的语句比MyArray[0] = new Rectangle(MyArray[0].X + 5, MyArray[0].Y, MyArray[0].Width, MyArray[0].Height)
更具可读性。
【讨论】:
【参考方案2】:如果您的数据很小(
.NET 中非原始值类型的示例是点结构 (System.Drawing)。
【讨论】:
【参考方案3】:每个变量都有一个生命周期。但并非每个变量都需要灵活性来让您的变量执行高而不是在堆中管理。
值类型 (Struct) 包含在堆栈中分配或在结构中内联分配的数据。引用类型(类)存储对值的内存地址的引用,并在堆上分配。
拥有值类型的目的是什么? 值类型处理简单数据的效率相当高,(应该用来表示不可变类型来表示值)
值类型的对象不能在垃圾回收堆上分配,代表对象的变量不包含指向对象的指针;变量包含对象本身。
通过复制结构而不是仅仅创建对它的新引用,我们可以获得什么?
如果复制结构,C# 会创建对象的新副本并将对象的副本分配给单独的结构实例。但是,如果您复制一个类,C# 会创建一个对该对象的引用的新副本,并将该引用的副本分配给单独的类实例。结构体不能有析构函数,但类可以有析构函数。
【讨论】:
【参考方案4】:值类型通常比引用类型更高效:
引用类型在取消引用时会为引用和性能消耗额外的内存
值类型不需要额外的垃圾回收。它与它所在的实例一起收集垃圾。方法中的局部变量在方法离开时被清理。
值类型数组与高速缓存结合使用非常有效。 (将整数数组与Integer
类型的实例数组相比较)
【讨论】:
【参考方案5】:单字节值类型的实例占用一个字节。一个引用类型占用了引用加上同步块和虚函数表的空间...
要复制引用,请复制四(或八)字节引用。要复制一个四字节整数,请复制一个四字节整数。复制小值类型并不比复制引用更昂贵。
垃圾收集器根本不需要检查不包含引用的值类型。垃圾收集器必须跟踪每个引用。
【讨论】:
参考地点也很重要。 我应该马上说清楚,但我指的是大于引用大小的值类型。关于您的第三点:方法结束时不会自动丢弃方法本地引用吗?和结构一样吗? @Acidic:当然,reference 被丢弃了。但所指的东西可能不是;其他东西可能正在引用它。 酸性,没有。这种优化称为逃逸分析。 AFAIK 这是在某些 JVM 上完成的,但不是在 Microsoft 的 JIT 上完成的。我不知道 Mono 的。 @Acidic:你是绝对正确的。值类型仅在少数情况下是合理的,并且这些情况大多由内置值类型覆盖。但是,当您确实需要它们时,它们非常方便。【参考方案6】:“创建参考”不是问题。这只是 32/64 位的副本。创建对象是昂贵的。实际上创建对象很便宜,但收集它却不是。
当值类型较小且经常被丢弃时,它们对性能有好处。它们可以非常有效地用于大型阵列。结构没有对象头。还有很多其他性能差异。
编辑:Eric Lippert 在 cmets 中举了一个很好的例子:“一百万字节的数组如果是值类型会占用多少字节?如果它们是引用类型会占用多少字节?”
我会回答:如果 struct packing 设置为 1,这样的数组将占用 100 万和 16 个字节(在 32 位系统上)。使用引用类型将需要:
array, object header: 12
array, length: 4
array, data: 4*(1 million) = 4m
1 million objects, headers = 12 * (1 million)
1 million objects, data padded to 4 bytes: 4 * (1 million)
这就是为什么在大型数组中使用值类型可能是个好主意。
【讨论】:
以上是关于值类型或引用类型的好处?的主要内容,如果未能解决你的问题,请参考以下文章