我将在代码中创建100,000个对象。它们很小,只有2个或3个属性。我将它们放在通用列表中,当它们存在时,将循环它们并检查value a
,也许更新value b
。
将这些对象创建为类还是结构更快/更好?
编辑
一个。属性是值类型(除了我认为的字符串?)
b。他们可能(我们不确定)有一个有效的方法
编辑2
我想知道:垃圾收集器是否平等地处理了堆和堆栈上的对象,或者工作原理有所不同?
我将在代码中创建100,000个对象。它们很小,只有2个或3个属性。我将它们放在通用列表中,当它们存在时,将循环它们并检查value a
,也许更新value b
。
将这些对象创建为类还是结构更快/更好?
编辑
一个。属性是值类型(除了我认为的字符串?)
b。他们可能(我们不确定)有一个有效的方法
编辑2
我想知道:垃圾收集器是否平等地处理了堆和堆栈上的对象,或者工作原理有所不同?
Answers:
难道更快创建这些对象的类或结构?
您是唯一可以确定该问题答案的人。两种方法都尝试一下,测量有意义的,以用户为中心的相关性能指标,然后您将知道更改是否对相关场景中的实际用户产生了有意义的影响。
结构消耗更少的堆内存(因为它们更小且更易于压缩,而不是因为它们“在堆栈上”)。但是,它们比参考副本需要更长的时间。我不知道您针对内存使用或速度的性能指标是什么;这里需要权衡,您就是知道这是什么的人。
它是更好的创建这些对象的类或结构?
也许是上课,也许是结构。作为一个经验法则:如果对象是:
1.小
2.逻辑上是一个不可变的值
3.有很多它们
然后我考虑将其构造为一个结构。否则我会坚持使用引用类型。
如果您需要更改结构的某些字段,通常最好构建一个构造函数,该构造函数返回正确设置了字段的整个新结构。这也许稍微慢一些(测量一下!),但是从逻辑上讲,更容易推理。
垃圾收集器是否平等地处理堆和堆栈上的对象?
不,它们不一样,因为堆栈上的对象是集合的根。垃圾收集器不需要询问“堆栈中的这个东西还活着吗?” 因为该问题的答案始终是“是的,它在堆栈上”。(现在,您不能依靠它来使对象保持活动状态,因为堆栈是实现的详细信息。允许抖动引入优化,例如,注册通常是堆栈值的内容,然后再将其保存在堆栈中因此GC并不知道它还活着。注册的对象可以在不再次读取保存在其上的寄存器后立即主动收集其后代。)
但是垃圾收集器的确必须将堆栈上的对象视为活动对象,就像将已知的活动对象视为活动对象一样。堆栈上的对象可以引用需要保持活动状态的堆分配对象,因此,为了确定活动集,GC必须将堆栈对象视为活动的堆分配对象。但是显然,出于压缩堆的目的,它们不被视为“活动对象”,因为它们首先不在堆中。
明白了吗?
readonly
)来进行优化。我不会让那影响对可变性的选择(理论上我对效率细节一窍不通,但实际上,我朝着效率迈进的第一步始终是尽我所能尽可能简单地保证正确性,因此不必在检查和边缘情况下浪费CPU周期和大脑周期,并在其中适当地进行可变或不可变会有所帮助),但这会抵消您所说的不变性可能会变慢的任何下意识的反应。
有时,struct
您不需要调用new()构造函数,而是直接分配字段,使其比通常更快。
例:
Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
list[i].id = i;
list[i].isValid = true;
}
比大约快2至3倍
Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
list[i] = new Value(i, true);
}
其中Value
是一个struct
具有两个字段(id
和isValid
)。
struct Value
{
int id;
bool isValid;
public Value(int i, bool isValid)
{
this.i = i;
this.isValid = isValid;
}
}
另一方面,这些项目需要移动或选定的值类型会影响您的复制速度。为了获得确切的答案,我怀疑您必须分析代码并进行测试。
list
,因为指定的代码不适用于List<Value>
。
结构似乎与类相似,但是您应该注意一些重要的区别。首先,类是引用类型,而结构是值类型。通过使用结构,您可以创建行为类似于内置类型的对象,并享受它们的好处。
在类上调用New运算符时,它将在堆上分配。但是,当实例化一个结构时,它会在堆栈上创建。这将提高性能。同样,您将不会像使用类那样处理对结构实例的引用。您将直接使用struct实例。因此,在将结构传递给方法时,它是按值而不是作为引用传递的。
更多内容:
http://msdn.microsoft.com/zh-CN/library/aa288471(VS.71).aspx
结构数组在堆上的一个连续内存块中表示,而对象数组被表示为一个连续的引用块,而实际对象本身在堆中的其他位置,因此这两个对象及其数组引用都需要内存。
在这种情况下,当您将它们放置在a中List<>
(并将a返回List<>
到数组中)时,使用结构在内存方面将更为有效。
(但是请注意,大型阵列会在大型对象堆上找到位置,如果它们的寿命很长,可能会对您的进程的内存管理产生不利影响。另外,请记住,内存并不是唯一的考虑因素。)
ref
关键字来处理此问题。
如果它们具有值语义,那么您应该使用结构。如果它们具有引用语义,那么您可能应该使用一个类。有一些例外,即使存在值语义,它们也大多倾向于创建类,但从那里开始。
至于您的第二次编辑,GC仅处理堆,但是堆空间比堆栈空间大得多,因此将内容放到堆栈上并不总是一个胜利。除此之外,结构类型列表和类类型列表都将以任何一种方式出现在堆上,因此在这种情况下这是无关紧要的。
编辑:
我开始认为邪恶一词是有害的。毕竟,如果不需要主动使类变为可变的,这是个坏主意,而且我也不排除使用可变的结构。尽管这是一个糟糕的主意,但通常几乎总是一个坏主意,但是在大多数情况下,它只是与值语义不一致,因此在给定情况下使用结构是没有意义的。
私有嵌套结构可能会有合理的例外,因此该结构的所有使用都被限制在非常有限的范围内。这不适用于这里。
确实,我认为“它会变异,所以结构不好”并没有比处理堆和栈好多少(即使确实经常出现错误,这至少会对性能产生影响)。“它是变异的,因此认为它具有值语义很可能没有任何意义,因此它是一个不好的结构”,只是略有不同,但重要的是,我认为。
最好的解决方案是先测量,然后再测量,然后再测量更多。可能会有您正在执行的操作的详细信息,可能使诸如“使用结构”或“使用类”之类的简单易懂的答案变得困难。
从本质上讲,结构只是字段的集合而已。在.NET中,有可能“假装”结构成为对象,并且对于每种结构类型,.NET都隐式定义了具有相同字段和方法的堆对象类型,这些字段和方法作为堆对象,其行为类似于对象。拥有对此类堆对象(“盒装”结构)的引用的变量将表现出引用语义,但是直接拥有结构的变量只是变量的集合。
我认为,结构与类的混淆很大程度上源于以下事实:结构具有两个非常不同的使用案例,它们应该具有非常不同的设计准则,但是MS准则并未区分它们。有时需要某种行为像一个对象;在这种情况下,尽管“ 16字节限制”应该更像24-32,但MS准则还是很合理的。但是,有时需要的是变量的汇总。用于此目的的结构应仅由一堆公共字段组成,并且可能Equals
覆盖,ToString
覆盖和IEquatable(itsType).Equals
实施。用作字段聚合的结构不是对象,也不应该假装为对象。从结构的角度来看,字段的含义应无非是“写入该字段的最后一件事”。任何其他含义应由客户代码确定。
例如,如果变量聚合结构具有成员Minimum
和Maximum
,则结构本身不应该保证Minimum <= Maximum
。将这样的结构作为参数接收的代码应表现得好像它是分别传递Minimum
和传递Maximum
值一样。Minimum
不大于要求的要求Maximum
应被视为类似于Minimum
不大于单独传递的参数的要求Maximum
。
有时要考虑的一个有用模式是让一个ExposedHolder<T>
类定义如下:
class ExposedHolder<T>
{
public T Value;
ExposedHolder() { }
ExposedHolder(T val) { Value = T; }
}
如果一个具有List<ExposedHolder<someStruct>>
,其中someStruct
是一个变量聚合结构,则可能会做类似的事情myList[3].Value.someField += 7;
,但是赋予myList[3].Value
其他代码将为其提供内容,Value
而不是为其进行更改。相反,如果使用a List<someStruct>
,则必须使用var temp=myList[3]; temp.someField += 7; myList[3] = temp;
。如果使用可变的类类型,则将内容暴露myList[3]
给外部代码将需要将所有字段复制到其他对象。如果使用了不可变的类类型或“对象样式”结构,则有必要构造一个新实例,该实例与myList[3]
其他实例someField
不同,然后将其存储到列表中。
还有一点需要注意:如果要存储大量类似的东西,最好将它们存储在可能嵌套的结构数组中,最好将每个数组的大小保持在1K到64K左右。结构数组是特殊的,在其中索引一个将直接引用其中的结构,因此可以说“ a [12] .x = 5;”。尽管可以定义类似数组的对象,但是C#不允许它们与数组共享这种语法。
使用类。
总的来说。为什么在创建值b时不更新值b?