本文主要是介绍500w 的引用类型和值类型到底有多大差异?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
大家在写代码的时候,相信有很多朋友对 struct
认知不是很足,导致能用 class
的地方绝对不用struct
,但大家有没有发现,最近的几个 C# 版本中,底层框架中有很多 class 的替代品,比如说:
Task 和 ValueTask
Tuple 和 ValueTuple。
本质上来说都是为了提少 GC 负担,提高程序性能。
今天就和大家简单聊下,struct 和 class 到底在内存占用上有多大差距,首先我们分别定义两个空类型,然后分别灌入 500w
。
class Program{static void Main(string[] args){var list = new List<Test>(5000000);var valueList = new List<ValueTest>(5000000);for (int i = 0; i < 5000000; i++){list.Add(new Test());valueList.Add(new ValueTest());}Console.WriteLine("结束");Console.ReadLine();}}class Test{}struct ValueTest{}
接下来用 windbg 看一下差异。
0:000> !clrstack -a
OS Thread Id: 0x4040 (0)Child SP IP Call Site
00000000001CE920 00007ffb8fb147bc System.Console.ReadLine() [/_/src/libraries/System.Console/src/System/Console.cs @ 629]
00000000001CE950 00007ffb2b4c621b ConsoleApp6.Program.Main(System.String[]) [D:\net5\ConsoleApp1\ConsoleApp6\Program.cs @ 24]PARAMETERS:args (0x00000000001CE9D0) = 0x000000000281a650LOCALS:0x00000000001CE9B8 = 0x000000000281b6780x00000000001CE9B0 = 0x000000000281b6980x00000000001CE9AC = 0x00000000004c4b400x00000000001CE9A0 = 0x00000000000000000x00000000001CE99C = 0x00000000000000000:000> !DumpObj /d 000000000281b678
Name: System.Collections.Generic.List`1[[ConsoleApp6.Test, ConsoleApp6]]
MethodTable: 00007ffb2b594240
EEClass: 00007ffb2b57f0b0
Size: 32(0x20) bytes
File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.13\System.Private.CoreLib.dll
Fields:MT Field Offset Type VT Attr Value Name
00007ffb2b597638 4001d3c 8 System.__Canon[] 0 instance 0000000012811038 _items
00007ffb2b48b258 4001d3d 10 System.Int32 1 instance 5000000 _size
00007ffb2b48b258 4001d3e 14 System.Int32 1 instance 5000000 _version
00007ffb2b597638 4001d3f 8 System.__Canon[] 0 static dynamic statics NYI s_emptyArray
0:000> !DumpObj /d 000000000281b698
Name: System.Collections.Generic.List`1[[ConsoleApp6.ValueTest, ConsoleApp6]]
MethodTable: 00007ffb2b594de8
EEClass: 00007ffb2b5a5ea0
Size: 32(0x20) bytes
File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.13\System.Private.CoreLib.dll
Fields:MT Field Offset Type VT Attr Value Name
00007ffb2b596c60 4001d3c 8 ...eApp6.ValueTest[] 0 instance 0000000014e36a70 _items
00007ffb2b48b258 4001d3d 10 System.Int32 1 instance 5000000 _size
00007ffb2b48b258 4001d3e 14 System.Int32 1 instance 5000000 _version
00007ffb2b596c60 4001d3f 8 ...eApp6.ValueTest[] 0 static dynamic statics NYI s_emptyArray
0:000> !objsize 000000000281b678
sizeof(000000000281B678) = 160000056 (0x9896838) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Test, ConsoleApp6]])
0:000> !objsize 000000000281b698
sizeof(000000000281B698) = 5000056 (0x4c4b78) bytes (System.Collections.Generic.List`1[[ConsoleApp6.ValueTest, ConsoleApp6]])
从输出中可以看到,list=160M
,而 valuelist=5M
居然相差 32 倍, 这种量级的差异,在高性能的场景下足以让我们充分考量了,对吧!
我相信有很多朋友应该能搞明白为什么会是 32 倍。真有不明白的同学,我再来分析一波吧。
先看struct,用 dp 0000000014e36a70
看内存地址。
0:000> !da 0000000014e36a70
Name: ConsoleApp6.ValueTest[]
MethodTable: 00007ffb2b596c60
EEClass: 00007ffb2b596be0
Size: 5000024(0x4c4b58) bytes
Array: Rank 1, Number of elements 5000000, Type VALUETYPE
Element Methodtable: 00007ffb2b594760
[0] 0000000014e36a80
[1] 0000000014e36a81
[2] 0000000014e36a82
[3] 0000000014e36a83
[4] 0000000014e36a84
[5] 0000000014e36a85
[6] 0000000014e36a86
[7] 0000000014e36a87
[8] 0000000014e36a88
[9] 0000000014e36a89
[10] 0000000014e36a8a
[11] 0000000014e36a8b
[12] 0000000014e36a8c
[13] 0000000014e36a8d
[14] 0000000014e36a8e
[15] 0000000014e36a8f
[16] 0000000014e36a90
...0:000> dp 0000000014e36a70
00000000`14e36a70 00007ffb`2b596c60 00000000`004c4b40
00000000`14e36a80 00000000`00000000 00000000`00000000
00000000`14e36a90 00000000`00000000 00000000`00000000
00000000`14e36aa0 00000000`00000000 00000000`00000000
00000000`14e36ab0 00000000`00000000 00000000`00000000
00000000`14e36ac0 00000000`00000000 00000000`00000000
00000000`14e36ad0 00000000`00000000 00000000`00000000
00000000`14e36ae0 00000000`00000000 00000000`00000000
从输出看,对于一个空 struct 而言在内存中只占用了 1byte
。
接下来看一下 引用类型
,用 dp 0000000012811038
即可。
0:000> dp 0000000012811038
00000000`12811038 00007ffb`2b596a80 00000000`004c4b40
00000000`12811048 00000000`028110e8 00000000`02811100
00000000`12811058 00000000`02811118 00000000`02811130
00000000`12811068 00000000`02811148 00000000`02811160
00000000`12811078 00000000`02811178 00000000`02812500
00000000`12811088 00000000`028128a8 00000000`028128c0
00000000`12811098 00000000`028128d8 00000000`028128f0
00000000`128110a8 00000000`02812908 00000000`028129e8
刚才也提到了两者相差32倍,也就是一个引用类型应该要占用 32byte
才对,是吧,那这个是怎么算的呢?首先在 64bit
平台引用类型的最小size=3*8=24byte
, 也即 **(对象头+方法表指针+空占位符)**, 这个 size
在 coreclr
中也是有 const 声明的, 剩下的 8byte 就是上面用 dp 命令看到的数组中的每一元素的 方法表指针
啦。
至此,大家都明白了吧。
这篇关于500w 的引用类型和值类型到底有多大差异?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!