C#对象大小开销

15

我正在优化内存消耗应用程序。在此背景下,我有一个关于C#引用类型大小开销的问题。

C#对象所占用的字节数等于其字段占用的字节数加上一些额外的管理开销。我推测这个管理开销在不同的.NET版本和实现中可能会有所不同。

您知道C#对象的管理开销大小是多少(如果overhead是可变的,则是最大值)(C# 4.0和Windows 7,8环境)吗?

管理开销是否因32位或64位.NET运行时而异?


2
如果你知道这个问题的答案,你能用它做什么? - Jon
4
为什么不直接使用内存分析器? - John Saunders
5
除非小类确实应该合并为一个类,否则不要通过将小类组合在一起来破坏您的应用程序架构。小类是好的。遵循@JohnSaunders的建议。如果您有内存问题,请使用内存分析器。不要猜测,而是要了解真相。 - Anthony Pegram
1
为了节省内存,您可以查看空转发值http://geekswithblogs.net/akraus1/archive/2011/08/18/146583.aspx。如果您以流式方式从磁盘加载许多类似的对象,则这非常方便。您以后可以应用更多技巧,如引用共享和其他内容。 - Alois Kraus
1
我投票支持重新开放这个问题,因为它不是重复的。链接的重复问题是关于在特定系统上运行时确定类的内存大小。而这个问题是关于包含在那个大小中的开销量以及在不同系统上的差异。 - HugoRune
显示剩余2条评论
3个回答

17

通常情况下,由GC分配的每个对象都有8或12字节的开销。在32位运行时中,同步块占用4字节,类型句柄占用4字节,在64位运行时中,这些开销是8字节。详细信息请参见MSDN杂志上的“ObjectInstance”部分的《深入了解.NET Framework内部以查看CLR如何创建运行时对象》

请注意,实际引用在32位或64位.NET运行时中也会发生变化。

此外,类型的调整填充可能会影响地址边界,不过这取决于所涉及的类型。这也可能导致对象之间出现“空白区域”,但是运行时(主要是通过StructLayoutAttribute)会决定数据对齐的时间和方式。


1
TypeHandle(但不包括syncblock,我想)是指针,所以在x64上应该是8个字节,对吧? - JerKimball
有一些优化64位指针为32位的方法;例如,如果所有类描述符信息块都适合于同一个4GB段中,则运行时解释器只需要存储一个偏移量到该段而不是完整宽度的指针。 - David R Tribble
1
@Loadmaster,这还没有完成,至少在当前的运行时中还没有。 - Reed Copsey
@ReedCopsey,链接“深入了解.NET Framework内部以查看CLR如何创建运行时对象”已经不存在了,您是否有任何新的信息或更新^^ +1 * 2 - Bassam Alugili
你能否帮我找一些文档、博客或链接,以帮助我理解这个语句 - “此外,为了适应地址边界,某些类型可能需要填充,尽管这取决于具体的类型。”? - RBT
显示剩余5条评论

9
有一篇在线文章的标题是"The Truth About .NET Objects And Sharing Them Between AppDomains",其中展示了一些rotor源代码以及通过普通指针在应用程序域之间共享对象的一些实验结果。

http://geekswithblogs.net/akraus1/archive/2012/07/25/150301.aspx

  • 所有32位版本的CLR需要12个字节
  • 所有64位版本的CLR需要24个字节

您可以通过向数组中添加数百万个对象(N)来轻松测试此功能。由于指针大小已知,因此可以通过将值除以N来计算对象大小。

var initial = GC.GetTotalMemory(true);
const int N = 10 * 1000 * 1000;
var arr = new object[N];
for (int i = 0; i < N; i++)
{
    arr[i] = new object();
}

var ObjSize = (GC.GetTotalMemory(false) - initial - N * IntPtr.Size) / N;

为了在您的.NET平台上获得大致值。

实际上,对象大小是定义的,以便GC可以对最小对象大小做出假设。

\sscli20\clr\src\vm\object.h

//
// The generational GC requires that every object be at least 12 bytes
// in size.   
#define MIN_OBJECT_SIZE     (2*sizeof(BYTE*) + sizeof(ObjHeader))

例如,32位表示最小对象大小为12字节,其中留下4字节的空洞。如果您将int添加到空类中,则该空洞将被填充,对象大小仍为12字节。

4

一个对象有两种开销:

  • 用于处理该对象的内部数据。
  • 数据成员之间的填充。

内部数据是两个指针,在32位应用程序中为8字节,在64位应用程序中为16字节。

数据成员被填充,使它们从偶数地址边界开始。例如,如果类中有一个byte和一个int,则byte可能会填充三个未使用的字节,以便int从下一个机器字边界开始。

类的布局由JIT编译器根据系统架构确定(并且可能在框架版本之间有所不同),因此对C#编译器来说是未知的。


1
@ReedCopsey:我无法从那篇文章中得出结论,但是例如这篇文章则说明了相反的情况:http://www.simple-talk.com/dotnet/.net-framework/object-overhead-the-hidden-.net-memory--allocation-cost/ - Guffa
@usr:你指的是什么? - Guffa
@usr:你有什么依据吗?根据我所发现的情况并非如此:http://www.simple-talk.com/dotnet/.net-framework/object-overhead-the-hidden-.net-memory--allocation-cost/ https://dev59.com/4Ggv5IYBdhLWcg3wXPkt - Guffa
@usr:我认为你混淆了32位和64位环境。32位内存管理器以16字节的块分配内存,但64位内存管理器不是这样的。 - Guffa
1
这篇文章证明了你是正确的。x64开销为16字节。我从哪里得到12字节的数字?我记得在网上多次看到过。 - usr
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接