我将在代码中创建100000个对象。它们是小的,只有2或3个属性。我将把它们放在一个通用列表中,当它们存在时,我将循环它们并检查值a,并可能更新值b。
将这些对象创建为类还是结构更快/更好?
编辑
a.属性是值类型(除了我认为的字符串?)
B.他们可能(我们还不确定)有一个验证方法
编辑2
我想知道:堆和堆栈上的对象是由垃圾收集器平均处理的,还是工作方式不同?
- 他们是只会有公共领域,还是也会有方法?类型是否为基元类型,例如整数?它们是包含在数组中还是列表之类的内容中?
- 如果有疑问,请使用类。如果需要在数组中自动初始化,请使用结构。
- 可变结构列表?当心迅猛龙。
- @米歇尔:GC永远不会碰堆栈。
- @leppie:那么"对象/结构"是如何从堆栈中移除的?
- @Leppie是真的,但是在堆栈上放置100000个对象也有其缺点。当然,如果它们在一个列表中,根据这个问题,它们无论如何都会在堆中。
- @安东尼:恐怕我错过了速射器的笑话。
- @米歇尔:就像大多数其他母语处理堆栈一样。堆栈指针的推送和弹出。
- @乔恩·汉娜:ASP.NET中只有一个问题(小堆栈,只有256K)。默认情况下比桌面应用小4倍。
- 速射笑话来自XKCD。但是,当你在讨论"值类型被分配到堆栈"的误解/实现细节时(如果适用,请删除),你需要注意的是EricLippet…
- @勒皮。做一些占你十分之一空间的事情,只能做10次。不是说我永远不会拿一大块(已经做了,会再做一次),但不能盲目地做。更关键的一点是,无论如何,这里的10万人都将生活在垃圾堆里。
- "B.它们可能(我们还不确定)有一个validate方法",因为结构应该是不可变的,您可以在构造函数中验证它们
- 速成摄影:imgs.xkcd.com/comics/goto.png
- 哈哈哈,+1是关于迅猛龙的笑话!
- 另请参见stackoverflow.com/questions/85553/…
Is it faster to create these objects as class or as struct?
你是唯一能决定那个问题答案的人。尝试两种方法,测量一个有意义的、以用户为中心的、相关的性能指标,然后您将知道在相关场景中,更改是否对实际用户有意义的影响。
结构消耗较少的堆内存(因为它们更小并且更容易压缩,而不是因为它们"在堆栈上")。但它们的复制时间比参考复制要长。我不知道您的性能指标对于内存使用或速度是什么;这里有一个折衷方案,您知道它是什么。
Is it better to create these objects as class or as struct?
也许是类,也许是结构。根据经验:如果对象是:1。小的2。逻辑上不可变的值三。他们有很多然后我会考虑把它变成一个结构。否则我会坚持使用引用类型。
如果需要改变某个结构的某个字段,通常最好构建一个构造函数,该构造函数返回一个字段设置正确的新结构。这可能稍微慢一点(测量一下!)但逻辑上讲起来容易多了。
Are objects on the heap and the stack processed equally by the garbage collector?
不,它们不同,因为堆栈上的对象是集合的根。垃圾收集器不需要问"堆栈上的这个东西还活着吗?"因为这个问题的答案总是"是的,它在堆栈上"。(现在,您不能依赖它来保持对象的活动性,因为堆栈是一个实现细节。允许抖动引入优化,例如注册通常是堆栈值的值,然后它从不在堆栈上,因此GC不知道它仍然存在。一旦保存在其上的寄存器不再被读取,则已注册对象的子代就可以被积极地收集。)
但是垃圾收集器必须将堆栈上的对象视为活动对象,就像它对待任何已知为活动对象一样。堆栈上的对象可以引用需要保持活动状态的堆分配对象,因此GC必须将堆栈对象视为活动的堆分配对象,以确定活动集。但显然,为了压缩堆,它们不会被视为"活动对象",因为它们首先不在堆中。
明白了吗?
- Eric,你知道编译器或抖动是否利用了不可变性(如果使用readonly强制的话)来进行优化。我不会让这影响到对易变性的选择(理论上,我是效率细节的疯子,但实际上,我朝着效率的第一步总是尽可能简单地保证正确性,因此不必在检查和边缘情况上浪费CPU周期和大脑周期,适当地可变或不可变有助于但它可以对抗任何膝跳的反应,因为你说不变可以变慢。
- @乔恩:C编译器优化了常量数据,而不是只读数据。我不知道JIT编译器是否对只读字段执行任何缓存优化。
- 遗憾的是,正如我所知道的,不变性的知识允许一些优化,但在那一点上达到了我理论知识的极限,但它们是我想要扩展的极限。同时,"这两种方法都可以更快,这就是为什么,现在测试并找出哪一种适用于本例"是有用的,可以这样说:)
- 我建议您阅读simple talk.com/dotnet/.net framework/…和您自己的文章(@eric):blogs.msdn.com/b/ericlippert/archive/2010/09/30/…,开始深入了解细节。周围还有许多其他的好文章。顺便说一句,在处理100000个小的内存对象方面的差异很难通过类的一些内存开销(约2.3MB)来发现。它可以很容易地通过简单的测试进行检查。
- 是的,很清楚。非常感谢您的全面(广泛是更好的?谷歌翻译提供了两种翻译。我想说的是,你花了时间来写一个简短的答案,但也花了时间来写所有的细节)答案。
有时使用struct时,您不需要调用new()构造函数,直接分配字段,使其比通常更快。
例子:
1 2 3 4 5 6
| Value[] list = new Value[N ];
for (int i = 0; i < N ; i ++)
{
list [i ].id = i ;
list [i ].is_valid = true;
} |
比这快2到3倍
1 2 3 4 5
| Value[] list = new Value[N ];
for (int i = 0; i < N ; i ++)
{
list [i ] = new Value(i, true);
} |
其中,Value是一个struct,有两个字段(id和is_valid)。
另一方面是需要移动的项或选定的值类型,所有这些复制都会减慢您的速度。为了得到准确的答案,我怀疑你必须分析你的代码并测试它。
- +一个很好的例子
- 显然,当您对本地边界进行值编组时,事情也会变得更快。
- +1为例
- 我建议使用除list以外的名称,因为所示代码不适用于List。
- var list2 = new List(list)工作得很好
结构可能看上去与阶级相似,但有重要的区别,你应该知道。首先,类别是参考类型,结构是价值型。通过结构,你可以创造出像建筑物类型一样的物体,并享受它们的利益。
当你在一个班级上呼叫新的操作员时,它会被分配到一个等级。然而,当你建立一个结构时,它会在堆栈上创造出来。这将带来收益。同时,你也不会被引用到一个机构的一个实例中,就像你是一个阶级。你将直接与组织机构合作。由于这一点,当经过一个结构到一种方法时,它通过一个参照值而转移。
这里更多
http://msdn.microsoft.com/en-us/library/a288471(vs.71)。
- 我知道它是在msdn上说的,但msdn并不是在讲整个故事。堆栈与堆是一个实现细节,结构并不总是在堆栈上进行。关于这个最近的博客,请参见:blogs.msdn.com/b/ericlippet/archive/2010/09/30/&hellip;
- "…它是按值传递的…"引用和结构都是按值传递的(除非其中一个使用"ref")—传递的是不同的值或引用,即结构是按值传递的,类对象是按值传递的,引用是按值传递的,引用是按参数传递的。
- 那篇文章在几个关键点上有误导性,我已经要求msdn团队修改或删除它。
- @Eric Lippert:您是否可以鼓励对对象实例(存储在堆中)和对象引用(存储在字段、变量中,或在任何地方)使用更独特的术语?此外,对于"可变结构是邪恶的",似乎可变结构大部分都是好的,除了临时结构被创建的地方。能够改变一些东西,安全地知道没有其他东西是它的别名,这似乎是一种有用的能力。当然,可以在整个地方克隆类对象,但这似乎相当浪费。
- 我认为结构上的可变属性是可以的(但不是很好),因为编译器通常捕获对临时副本属性的赋值,但可变方法绝对是邪恶的。如果出于性能原因需要它们,我将使用带有引用参数的静态方法,而不是修改这个参数
- @supercat:要解决第一个问题:更大的问题是,在托管代码中,存储值或对值的引用在很大程度上是不相关的。我们一直在努力制作一个内存模型,大多数时候,它允许开发人员允许运行时代表他们做出智能存储决策。当不理解这些区别会产生崩溃的后果时,这些区别就非常重要了,就像在C中一样;而在C中则不那么重要。
- @Supercat:为了解决你的第二点,没有可变的结构是最邪恶的。例如,void m()s=new s();s.blah();n(s);。重构为:void doblah(s)s.blah();void m(s=new s();doblah(s);n(s);。因为S是一个可变结构,所以引入了一个bug。你立刻看到虫子了吗?或者S是一个可变结构的事实是否隐藏了这个bug?
- @埃里克·利珀特:我认为许多习惯于其他语言中的价值语义的人会被诸如"car2=car1;car2.color=blue";"影响car1"之类的东西所迷惑。如果人们认为car1和car2是持有VIN(车辆ID)而不是实际车辆,那么语义是有意义的。VIN没有颜色。VIN代表的汽车有一种颜色。说"把车漆成1G1KXQ58J绿色"并不意味着我们应该把数字漆成绿色——这意味着我们应该找到有那个VIN的车并把它漆成绿色。说"car2=car1"只是复制VIN,而不是汽车本身。
- @埃里克·利珀特:在后一种情况下,错误是显而易见的;多布拉需要通过引用接受这个结构。有一些微妙的错误案例,比如改变结构的方法(邪恶),但是假设一个人需要拥有1000000个项目,每个项目有10个16位部分,并且经常需要改变这些部分的一半的不同组合。可变结构将非常有效。签出时进行一次复制操作,签入时进行一次复制操作。非可变结构似乎需要为每个编辑制作一个副本,除非有许多不同的"更改"功能。
- @埃里克·利珀特:此外,我认为更常见的bug场景是可变类发生的情况,例如,如果有人在将对象存储到字典中之前忘记克隆对象。结构不会发生这种情况。我倾向于认为结构应该是可变的,如果它们是普通的旧数据,但看不出POD结构有什么问题。(顺便说一句,回到你的例子,我假设blah()是一个改变结构的邪恶方法——我百分之百同意你说改变结构的方法是一个坏主意)。
Arrays of structs are represented on the heap in a continuous block of memory,whereas an array of objects is represented as a continuous block of references with the actual objects themselves elsewhere on the heap,thus requiring memory for both the objects and for their array referen
在这个例子中,当你把它们放在一个List<>(和一个EDOCX1〕〔0〕的阵列中时,它会更有效,更便于使用。
(尽管如此,大型阵列将找到它们在大型物体的热点,如果它们的生命很长,可能对你的过程的记忆管理产生不利影响。)还记得,记忆不是唯一的考虑。)
- 您可以使用ref关键字来处理这个问题。
- "不过要小心,大型数组会在大型对象堆中找到自己的方法,如果它们的寿命很长,可能会对进程的内存管理产生不利影响。"—我不太确定您为什么会这么认为?在LOH上分配不会对内存管理造成任何不利影响,除非(可能)它是一个短期对象,并且您希望在不等待第2代收集的情况下快速回收内存。
- @乔恩·阿图斯:洛伊不会被压缩。任何长寿的物体都会将LOH划分为之前和之后的空闲内存区域。分配需要连续内存,如果这些区域不够大,无法进行分配,则会向LOH分配更多内存(即,您将获得LOH碎片)。
如果它们有价值的话,那么你可能会使用一个结构。如果他们有参考语种,那么你可能需要使用一个分类。有一些例外,即使有价值的语种,也能从那里开始创造一个阶级。
就你的第二版而言,GC只处理热量,但空间比堆积空间更热量,所以把东西放到堆积空间并不总是一个温泉。最好的是,一份结构类型的清单和一份分类类型的清单将以第二种方式出现,因此这在本案中是不可接受的。
编辑:
我开始考虑到这一点。毕竟,如果不需要活跃的话,做一个变异的类别是一个坏主意,而我也不会用一个变异的结构来管理它。这是一个穷人的想法,常常是一个坏的想法,但最重要的是,它并不与价值观相吻合,因此,它并不意味着在第二个案例中使用一个结构。
在私营企业结构中,可以有合理的例外情况,在这种结构中,所有的使用都被限制在极为有限的范围内。这不是在这里。
事实上,我认为"这是一个坏的学习方法,它并不是一个更好的方法,它涉及的是头部和堆栈(在最起码的情况下,它具有某种影响,即使一个经常被误解的人)。"它变异了,所以它不应该把它看作是有价值的语义,所以它是一个坏的结构"只是略有不同,但重要的是我认为。
结构的本质就是字段的集合。在.NET中,结构可以"假装"为对象,并且对于每种结构类型.NET都隐式定义了具有相同字段和方法的堆对象类型,这些字段和方法(作为堆对象)的行为将类似于对象。保存对此类堆对象的引用的变量("boxed"结构)将显示引用语义,但直接保存结构的变量只是变量的聚合。
我认为结构与类的混淆很大程度上源于这样一个事实:结构有两个非常不同的用例,它们应该有非常不同的设计准则,但是MS准则没有区分它们。有时需要一些类似于对象的行为;在这种情况下,MS准则是相当合理的,尽管"16字节限制"可能更像24-32。然而,有时需要的是变量的聚合。用于此目的的结构应该简单地由一组公共字段组成,并且可能包含一个Equals重写、ToString重写和IEquatable(itsType).Equals实现。用作字段聚合的结构不是对象,不应假装是。从结构的角度来看,字段的含义不应大于或小于"写入此字段的最后一件事"。任何附加的含义应由客户代码决定。
例如,如果变量聚合结构的成员为Minimum和Maximum,则该结构本身不应承诺Minimum <= Maximum。接收这样一个结构作为参数的代码,其行为应该像传递单独的Minimum和Maximum值一样。要求Minimum不大于Maximum应视为要求Minimum参数不大于单独传递的Maximum参数。
有时需要考虑的一个有用模式是让一个ExposedHolder类定义如下:
1 2 3 4 5 6
| class ExposedHolder<T>
{
public T Value;
ExposedHolder() { }
ExposedHolder(T val) { Value = T; }
} |
如果一个人有一个List>,其中someStruct是一个变量聚合结构,那么他可以做myList[3].Value.someField += 7;之类的事情,但是把myList[3].Value交给其他代码将使它得到Value的内容,而不是给它一种改变它的方法。相比之下,如果使用List,则需要使用var temp=myList[3]; temp.someField += 7; myList[3] = temp;。如果使用可变类类型,将myList[3]的内容暴露于外部代码将需要将所有字段复制到其他对象。如果使用不可变的类类型或"object-style"结构,则需要构造一个与myList[3]相似的新实例(someField不同),然后将该新实例存储到列表中。
另一个注意事项是:如果要存储大量类似的东西,最好将它们存储在可能嵌套的结构数组中,最好将每个数组的大小保持在1K到64K左右。结构数组是特殊的,在索引中,可以直接引用内部的结构,因此可以说"a[12].x=5;"。虽然可以定义类似数组的对象,但C不允许它们与数组共享这种语法。
最好的解决办法是再测量一次,再测量一次。也许有一些细节说明你在做什么可以简化,简单的答案是"Use structures"or"use classes"difficult.
- 同意度量部分,但在我看来,这是一个直截了当、明确的例子,我认为也许可以说一些通用的东西。结果,一些人做到了。
好吧,如果您最终使用struct,那么去掉字符串并使用固定大小的char或byte缓冲区。
那是:表演。
从C++的角度来看,我同意与类相比,修改结构属性会慢一些。但我确实认为,由于结构是在堆栈上而不是堆上分配的,所以它们的读取速度会更快。从堆中读取数据比从堆栈中读取数据需要更多的检查。
使用阶级。
我们有通用的笔记为什么不更新B作为创建他们?