500w 的引用类型和值类型到底有多大差异?

2023-11-05 18:58
文章标签 类型 引用 差异 到底 500w

本文主要是介绍500w 的引用类型和值类型到底有多大差异?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家在写代码的时候,相信有很多朋友对 struct 认知不是很足,导致能用 class 的地方绝对不用struct,但大家有没有发现,最近的几个 C# 版本中,底层框架中有很多 class 的替代品,比如说:

  1. Task 和 ValueTask

  2. 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, 也即 **(对象头+方法表指针+空占位符)**, 这个 sizecoreclr 中也是有 const 声明的, 剩下的 8byte 就是上面用 dp 命令看到的数组中的每一元素的 方法表指针 啦。

至此,大家都明白了吧。

这篇关于500w 的引用类型和值类型到底有多大差异?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/351847

相关文章

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1

MySQL 索引简介及常见的索引类型有哪些

《MySQL索引简介及常见的索引类型有哪些》MySQL索引是加速数据检索的特殊结构,用于存储列值与位置信息,常见的索引类型包括:主键索引、唯一索引、普通索引、复合索引、全文索引和空间索引等,本文介绍... 目录什么是 mysql 的索引?常见的索引类型有哪些?总结性回答详细解释1. MySQL 索引的概念2

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Java获取当前时间String类型和Date类型方式

《Java获取当前时间String类型和Date类型方式》:本文主要介绍Java获取当前时间String类型和Date类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录Java获取当前时间String和Date类型String类型和Date类型输出结果总结Java获取