.NET阵列的开销?

我试图使用以下代码确定.NET数组(在32位进程中)标头的开销:


long bytes1 = GC.GetTotalMemory(false);

object[] array = new object[10000];

    for (int i = 0; i < 10000; i++)

        array[i] = new int[1];

long bytes2 = GC.GetTotalMemory(false);

array[0] = null; // ensure no garbage collection before this point


Console.WriteLine(bytes2 - bytes1);

// Calculate array overhead in bytes by subtracting the size of 

// the array elements (40000 for object[10000] and 4 for each 

// array), and dividing by the number of arrays (10001)

Console.WriteLine("Array overhead: {0:0.000}", 

                  ((double)(bytes2 - bytes1) - 40000) / 10001 - 4);

Console.Write("Press any key to continue...");

Console.ReadKey();

结果是


    204800

    Array overhead: 12.478

在32位进程中,object [1]的大小应与int [1]相同,但是实际上开销跳升了3.28个字节,


    237568

    Array overhead: 15.755

有人知道为什么吗?


(顺便说一句,如果有人好奇的话,非数组对象(例如,上面循环中的(object)i)的开销约为8个字节(8.384)。我听说在64位进程中为16个字节。)


ibeautiful
浏览 412回答 3
3回答

神不在的星期二

数组是引用类型。所有引用类型都带有两个附加的单词字段。类型引用和SyncBlock索引字段,除其他外,这些字段用于实现CLR中的锁定。因此,引用类型的类型开销是32位上的8个字节。最重要的是,数组本身还存储了另外4个字节的长度。这使总开销达到12个字节。我刚刚从乔恩·斯凯特(Jon Skeet)的答案中学到,引用类型的数组还有4个字节的额外开销。可以使用WinDbg确认。事实证明,附加字是存储在数组中的类型的另一个类型引用。所有引用类型的数组都在内部存储为object[],并附加了对实际类型的类型对象的引用。因此,a string[]实际上只是一个object[]带有对type的附加类型引用的a string。有关详细信息,请参见下文。存储在数组中的值:引用类型的数组保存对对象的引用,因此数组中的每个条目都是引用的大小(即32位为4字节)。值类型的数组内联存储值,因此每个元素都将占用所讨论类型的大小。这个问题可能也很有趣:C#List <double> size vs double [] size血腥细节考虑以下代码var strings = new string[1];var ints = new int[1];strings[0] = "hello world";ints[0] = 42;附加WinDbg显示以下内容:首先,让我们看一下值类型数组。0:000> !dumparray -details 017e2acc&nbsp;Name: System.Int32[]MethodTable: 63b9aa40EEClass: 6395b4d4Size: 16(0x10) bytesArray: Rank 1, Number of elements 1, Type Int32Element Methodtable: 63b9aaf0[0] 017e2ad4&nbsp; &nbsp; Name: System.Int32&nbsp; &nbsp; MethodTable 63b9aaf0&nbsp; &nbsp; EEClass: 6395b548&nbsp; &nbsp; Size: 12(0xc) bytes&nbsp; &nbsp; &nbsp;(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)&nbsp; &nbsp; Fields:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MT&nbsp; &nbsp; Field&nbsp; &nbsp;Offset&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Type VT&nbsp; &nbsp; &nbsp;Attr&nbsp; &nbsp; Value Name&nbsp; &nbsp; 63b9aaf0&nbsp; 40003f0&nbsp; &nbsp; &nbsp; &nbsp; 0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.Int32&nbsp; 1 instance&nbsp; &nbsp; &nbsp; &nbsp;42 m_value <=== Our value0:000> !objsize 017e2acc&nbsp;sizeof(017e2acc) =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;16 (&nbsp; &nbsp; &nbsp; &nbsp; 0x10) bytes (System.Int32[])0:000> dd 017e2acc -0x4017e2ac8&nbsp; 00000000 63b9aa40 00000001 0000002a <=== That's the value首先,我们转储数组和值为42的一个元素。可以看出,大小为16个字节。也就是说,int32值本身为4个字节,常规引用类型的开销为8个字节,数组的长度为另外4个字节。原始转储显示SyncBlock,方法表int[],长度和值42(十六进制2a)。请注意,SyncBlock位于对象引用的前面。接下来,让我们看一下,string[]找出附加词的用途。0:000> !dumparray -details 017e2ab8&nbsp;Name: System.String[]MethodTable: 63b74ed0EEClass: 6395a8a0Size: 20(0x14) bytesArray: Rank 1, Number of elements 1, Type CLASSElement Methodtable: 63b988a4[0] 017e2a90&nbsp; &nbsp; Name: System.String&nbsp; &nbsp; MethodTable: 63b988a4&nbsp; &nbsp; EEClass: 6395a498&nbsp; &nbsp; Size: 40(0x28) bytes <=== Size of the string&nbsp; &nbsp; &nbsp;(C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)&nbsp; &nbsp; String:&nbsp; &nbsp; &nbsp;hello world&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; Fields:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MT&nbsp; &nbsp; Field&nbsp; &nbsp;Offset&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Type VT&nbsp; &nbsp; &nbsp;Attr&nbsp; &nbsp; Value Name&nbsp; &nbsp; 63b9aaf0&nbsp; 4000096&nbsp; &nbsp; &nbsp; &nbsp; 4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.Int32&nbsp; 1 instance&nbsp; &nbsp; &nbsp; &nbsp;12 m_arrayLength&nbsp; &nbsp; 63b9aaf0&nbsp; 4000097&nbsp; &nbsp; &nbsp; &nbsp; 8&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;System.Int32&nbsp; 1 instance&nbsp; &nbsp; &nbsp; &nbsp;11 m_stringLength&nbsp; &nbsp; 63b99584&nbsp; 4000098&nbsp; &nbsp; &nbsp; &nbsp; c&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.Char&nbsp; 1 instance&nbsp; &nbsp; &nbsp; &nbsp;68 m_firstChar&nbsp; &nbsp; 63b988a4&nbsp; 4000099&nbsp; &nbsp; &nbsp; &nbsp;10&nbsp; &nbsp; &nbsp; &nbsp; System.String&nbsp; 0&nbsp; &nbsp;shared&nbsp; &nbsp;static Empty&nbsp; &nbsp; >> Domain:Value&nbsp; 00226438:017e1198 <<&nbsp; &nbsp; 63b994d4&nbsp; 400009a&nbsp; &nbsp; &nbsp; &nbsp;14&nbsp; &nbsp; &nbsp; &nbsp; System.Char[]&nbsp; 0&nbsp; &nbsp;shared&nbsp; &nbsp;static WhitespaceChars&nbsp; &nbsp; >> Domain:Value&nbsp; 00226438:017e1760 <<0:000> !objsize 017e2ab8&nbsp;sizeof(017e2ab8) =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;60 (&nbsp; &nbsp; &nbsp; &nbsp; 0x3c) bytes (System.Object[]) <=== Notice the underlying type of the string[]0:000> dd 017e2ab8 -0x4017e2ab4&nbsp; 00000000 63b74ed0 00000001 63b988a4 <=== Method table for string017e2ac4&nbsp; 017e2a90 <=== Address of the string in memory0:000> !dumpmt 63b988a4EEClass: 6395a498Module: 63931000Name: System.StringmdToken: 02000024&nbsp; (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)BaseSize: 0x10ComponentSize: 0x2Number of IFaces in IFaceMap: 7Slots in VTable: 196首先,我们转储数组和字符串。接下来,我们转储的大小string[]。请注意,WinDbg在System.Object[]此处列出了类型。在这种情况下,对象大小包括字符串本身,因此总大小是数组中的20加上字符串的40。通过转储实例的原始字节,我们可以看到以下内容:首先是SyncBlock,然后是的方法表object[],然后是数组的长度。之后,我们通过引用方法表找到了另外的4个字节。如上所示,可以通过dumpmt命令对此进行验证。最后,我们找到对实际字符串实例的单个引用。结论数组的开销可以按如下方式分解(即32位)4字节的SyncBlock数组本身的方法表(类型引用)为4个字节数组长度为4个字节引用类型的数组又添加了4个字节来保存实际元素类型的方法表(引用类型的数组位于内部object[])即,对于值类型数组,开销是12个字节,对于引用类型数组,开销是16个字节。

达令说

我们有一个项目,可处理大量数据(最大2GB)。作为主要存储,我们使用Dictionary<T,T>。实际上创建了数千个字典。将其更改List<T>为键和List<T>值(我们IDictionary<T,T>自己实现)后,内存使用量减少了约30-40%。
打开App,查看更多内容
随时随地看视频慕课网APP