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

相关文章

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

Python如何查看数据的类型

《Python如何查看数据的类型》:本文主要介绍Python如何查看数据的类型方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python查看数据的类型1. 使用 type()2. 使用 isinstance()3. 检查对象的 __class__ 属性4.

Python容器类型之列表/字典/元组/集合方式

《Python容器类型之列表/字典/元组/集合方式》:本文主要介绍Python容器类型之列表/字典/元组/集合方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 列表(List) - 有序可变序列1.1 基本特性1.2 核心操作1.3 应用场景2. 字典(D

Python如何在Word中生成多种不同类型的图表

《Python如何在Word中生成多种不同类型的图表》Word文档中插入图表不仅能直观呈现数据,还能提升文档的可读性和专业性,本文将介绍如何使用Python在Word文档中创建和自定义各种图表,需要的... 目录在Word中创建柱形图在Word中创建条形图在Word中创建折线图在Word中创建饼图在Word

SpringBoot接收JSON类型的参数方式

《SpringBoot接收JSON类型的参数方式》:本文主要介绍SpringBoot接收JSON类型的参数方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、jsON二、代码准备三、Apifox操作总结一、JSON在学习前端技术时,我们有讲到过JSON,而在

Mysql中InnoDB与MyISAM索引差异详解(最新整理)

《Mysql中InnoDB与MyISAM索引差异详解(最新整理)》InnoDB和MyISAM在索引实现和特性上有差异,包括聚集索引、非聚集索引、事务支持、并发控制、覆盖索引、主键约束、外键支持和物理存... 目录1. 索引类型与数据存储方式InnoDBMyISAM2. 事务与并发控制InnoDBMyISAM

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2