500w 的引用类型和值类型到底有多大差异?
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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) = 0x000000000281a650
LOCALS:
0x00000000001CE9B8 = 0x000000000281b678
0x00000000001CE9B0 = 0x000000000281b698
0x00000000001CE9AC = 0x00000000004c4b40
0x00000000001CE9A0 = 0x0000000000000000
0x00000000001CE99C = 0x0000000000000000
0: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 的引用类型和值类型到底有多大差异?的主要内容,如果未能解决你的问题,请参考以下文章