.NET中的值类型与引用类型

.NET中的值类型与引用类型

这是一个常见面试题,值类型(Value Type)和引用类型(Reference Type)有什么区别?他们性能方面有什么区别?

TL;DR(先看结论)


值类型引用类型
创建位置托管堆
赋值时复制值复制引用
动态内存分配需要分配内存
额外内存消耗32位:额外12字节;64位:24字节
内存分布连续分散

引用类型

常用的引用类型代码示例:

void Main(){    // 开始计数器    var sw = Stopwatch.StartNew();    long memory1 = GC.GetAllocatedBytesForCurrentThread();    // 创建C16    Span<B16> data = new B16[40_0000];    foreach (ref B16 item in data)    {        item = new B16();        item.V15.V15.V0 = 1;    }    long sum = 0; // 求和以免代码被优化掉    for (var i = 0; i < data.Length; ++i)    {        sum += data[i].V15.V15.V0;    }    // 终止计数器    sw.Stop();    long memory2 = GC.GetAllocatedBytesForCurrentThread();    // 输出显示结果    new { Sum = sum, CreateTime = sw.ElapsedMilliseconds, Memory = memory2 - memory1 }.Dump();}class A1{    public byte V0;}class A16{    public A1 V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15;    public A16()    {        V0 = new A1(); V1 = new A1(); V2 = new A1(); V3 = new A1();        V4 = new A1(); V5 = new A1(); V6 = new A1(); V7 = new A1();        V8 = new A1(); V9 = new A1(); V10 = new A1(); V11 = new A1();        V12 = new A1(); V13 = new A1(); V14 = new A1(); V15 = new A1();    }}class B16{    public A16 V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15;    public B16()    {        V0 = new A16(); V1 = new A16(); V2 = new A16(); V3 = new A16();        V4 = new A16(); V5 = new A16(); V6 = new A16(); V7 = new A16();        V8 = new A16(); V9 = new A16(); V10 = new A16(); V11 = new A16();        V12 = new A16(); V13 = new A16(); V14 = new A16(); V15 = new A16();    }}

这次代码中,我们创建了40万个B16类型,然后对这40万个B16进行了统计,其中:

  • A1是一个字节(byte)的class

  • A16是包含16个A1的class

  • B16是包含16个A16的class

可以计算出,B16=16·A16=16x16·A1=16x16x256 bytes,一共分配了40万个B16,所以一共有40_0000x256=1_0240_0000 bytes,或约100兆字节

实际结果输出

SumCreateTimeMemory
40_00008_6813_440_000_304

电脑配置(之后的下文的性能测试结果与此完全相同):

项目/配置配置说明
CPUE3-1230 v3 @ 3.30GHz未超频
内存24GB DDR3 1600 MHz8GB x 3
.NET Core3.0.100-preview7-01282164位
软件LINQPad 6.0.1364位,optimize+


数字涵义:

  • 40万条数据对1求和,结果是40万,正确;

  • 总花费时间一共需要9417毫秒;

  • 总内存开销约为3.4GB。

请注意看内存开销,我们预估值是100MB,但实际约为3.4GB,这说明了引用类型需要(较大的)额外内存开销。

一个空对象 要分配多大的堆内存?

以一个空白引用类型为例,可以写出如下代码(LINQPad中运行):

long m1 = GC.GetAllocatedBytesForCurrentThread();var obj = new object();long m2 = GC.GetAllocatedBytesForCurrentThread();(m2 - m1).Dump();GC.KeepAlive(obj);

注意GC.KeepAlive是有必要的,否则运行在optimize+环境下会将new object()优化掉。

运行结果:24(在32位系统中,运行结果为:12

空引用类型(64位)为何要24个字节?

一个引用类型的堆内存包含以下几个部分:

  • 同步块索引(synchronization block index),8个字节,用于保存大量与CLR相关的元数据,以下基本操作都会用到该内存:

    • 线程同步(lock

    • 垃圾回收(GC

    • 哈希值(HashCode

    • 其它

  • 方法表指针(method table pointer),又叫类型对象指针(TypeHandle),8个字节,用来指向类的方法表;

  • 实例成员,8字节对齐,没有任何成员时也需要8个字节。

由于以上几点,才导致一个空白的object需要24个字节。

  • 因为没有同步块索引,导致:

    • 值类型不能参与线程同步(lock

    • 值类型不需要进行垃圾回收(GC

    • 值类型的哈希值计算过程与引用类型不同(HashCode

  • 因为没有方法表指针,导致:

    • 值类型不能继承

值类型的性能

值类型代码示例

void Main(){    // 开始计数器    var sw = Stopwatch.StartNew();    long memory1 = GC.GetAllocatedBytesForCurrentThread();    // 创建C16    Span<B16> data = new B16[40_0000];    foreach (ref B16 item in data)    {        // item = new B16();        item.V15.V15.V0 = 1;    }    long sum = 0; // 求和以免代码被优化掉    for (var i = 0; i < data.Length; ++i)    {        sum += data[i].V15.V15.V0;    }    // 终止计数器    sw.Stop();    long memory2 = GC.GetAllocatedBytesForCurrentThread();    // 输出显示结果    new { Sum = sum, CreateTime = sw.ElapsedMilliseconds, Memory = memory2 - memory1 }.Dump();}struct A1{    public byte V0;}struct A16{    public A1 V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15;}struct B16{    public A16 V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15;}

几乎完全一样的代码,区别只有:

  • 将所有的class(表示引用类型)关键字换成了struct(表示值类型)

  • item = new B16()语句去掉了(因为值类型创建数组会自动调用默认构造函数)

运行结果

运行结果如下:

SumCreateTimeMemory
40_000032102_400_024

注意,分配内存只有102_400_024字节,比我们预估的102_400_000只多了24个字节。这是因为数组也是引用类型,引用类型需要至少24个字节。

比较


运行时间时间比分配内存内存比
值类型32/102_400_024/
引用类型8_681271.28x3_440_000_30433.59x

在这个示例中,仅将值类型改成引用类型,竟需要多出271倍的时间,和33倍的内存占用。

重新审视值类型

值类型这么好,为什么不全改用值类型呢?

值类型的优点,恰恰也是值类型的缺点,值类型赋值时是复制值,而不是复制引用,而当值比较大时,复制值非常昂贵

在远古时代,甚至是没有动态内存分配的,所以世界上只有值类型。那时为了减少值类型复制,会用变量来保存对象的内存位置,可以说是最早的指针了。

在近代的的C里,除了值类型,还加入了指向动态分配的值类型的指针。其中指针基本可以与引用类型进行类比:

  • ✔指针和引用类型的引用,都指向真实的对象内存位置

  • ❌动态分配的内存需要手动删除,引用类型会自动GC回收

  • ❌指针指向的内存位置不会变,引用类型指向的内存位置会随着GC的内存压缩而产生变化,可用fixed关键字临时禁止内存压缩

  • ❌指针指向的内存没有额外消耗,引用类型需要分配至少24字节的堆内存

C++为了解决这个问题,也是卯足了劲。先是加入了值引用运算符 &,而后又发布了一版又一版的“智能”指针,如auto_ptr/shared_ptr/unique_ptr。但这些“智能”指针都需要提前了解它的使用场景,如:

  • 有对象所有权还是没有对象所有权?

  • 线程安全还是不安全?

  • 能否用于赋值?

而且库与库之前的版本多样,不统一,还影响开发的心情。

所以引用类型的优势就出来了,不用关心对象的所有权,不用关心线程安全,不用关心赋值问题,而且最重要的,还不用关心值类型复制的性能问题。

C#中的值类型支持

引用类型是如此好,以至于平时完全不需要创建值类型,就能完成任务了。但为什么值类型仍然还是这么重要呢?就是因为一旦涉及底层,性能关键型的服务器、游戏引擎等等,都需要关心内存分配,都需要使用值类型。

因为只有C#才能不依赖于C/C++等“本机语言”,就可写出性能关键型应用程序。

C#因为有这些和值类型的特性,导致与其它语言(C/C++)相比时完全不虚:

  • 首先,C#可以写自定义值类型

  • C# 7.0 值类型Task(ValueTask):大量异步请求,如读取流时,可以节省堆内存分配和GC
    链接:https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/

  • C# 7.0 ref返回值/本地变量引用:避免了大值类型内存大量复制的开销(有点像C++&关键字了)
    链接:https://devblogs.microsoft.com/dotnet/whats-new-in-csharp-7-0/#user-content-ref-returns-and-locals

  • C# 7.0 Span<T>Memory<T>,简化了ref引用的代码,甚至让foreach循环都可以操作修改值类型了
    链接:https://docs.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines

  • C# 7.2 加入in修饰符和其它修饰符,相当于C++中的const TypeName&

    链接:https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-7-2#safe-efficient-code-enhancements

  • C# 8.0 - Preview 5 可Dispose的ref struct,值类型也能使用Dispose模式了
    链接:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#disposable-ref-structs

ASP.NET Core曾使用Libuv(基于C语言)作为内部传输层,但从ASP.NET Core 2.1之后,换成了用.NET重写,链接:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.2#transport-configuration

最后的话

开发经常拿C#与同样开发Web应用的其它语言作比较,但由于缺乏对值类型的支持,这些语言没办法与C#相比。

其中Java还暂不支持自定义值类型。

推荐书籍:《C#从现象到本质》(郝亦非 著)

640?wx_fmt=jpeg


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314578.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Canada Cup 2016 C. Hidden Word 字符串构造

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个长度为272727的字符串sss&#xff0c;其中262626个字母每种都至少出现一次&#xff0c;让你构造一个213213213的矩阵&#xff0c;使得每个字母都出现一次&#xff0c;并且存在一条路径&#xff0c;经…

E:Johnny and Grandmaster

Johnny and Grandmaster 或许更好的阅读体验 思路 这道题就是把一组数分成两个集合&#xff0c;使这两个集合的对p的次方的和的差的最小值&#xff0c;也就是求sum1−sum2sum1 - sum2sum1−sum2得最小值&#xff0c; 由于结果过大我们可能需要对结果取模。那么这题得关键在于…

跨语言调用Hangfire定时作业服务

背景Hangfire允许您以非常简单但可靠的方式执行后台定时任务的工作。内置对任务的可视化操作。非常方便。但令人遗憾的是普遍都是业务代码和hagnfire服务本身聚合在一个程序中运行&#xff0c;极大的限制了hangfire的扩展和跨语言调用。所以萌生了开发一个支持restful api调用的…

Codeforces Round #453 (Div. 1) D. Weighting a Tree 构造 + dfs树

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一颗nnn个点的图&#xff0c;每个点都有一个点权cic_ici​&#xff0c;要求你给每个边赋一个权值kik_iki​&#xff0c;要求对于每个点与他相连的边的权值之和等于这个点的点权cic_ici​。 n≤1e5,n−1≤…

Codeforces Round #648 (Div. 2)(A, B, C, D)

Codeforces Round #648 (Div. 2) 或许更好的阅读体验 A:Matrix Game 思路 题意可以说是非常简单的&#xff0c;我们选定的格子的行列都不能存在1&#xff0c;可以发现我们可以放的格子一定是固定的&#xff0c;然后这题就变成了技术总共可以放多少个棋子了&#xff0c;所以…

可落地微服务on k8s的持续集成/部署方案

我们隔一流的软件生产工艺还有多远&#xff1f;在距离15000公里外&#xff0c;Amazon一年可以进行5000万次部署&#xff0c;在这一边某电商平台的研发部门里&#xff0c;让他们引以为傲的是他们正在进行“敏捷”开发模式&#xff0c;并对外号称他们是以每周为迭代来进行升级。时…

Codeforces Round #579 (Div. 3) F1. Complete the Projects (easy version) 排序 + 贪心

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 比较直观的想法就是对于bi≥0b_i\ge0bi​≥0的项目&#xff0c;我们将aia_iai​从小到大排序&#xff0c;让后依次加bib_ibi​&#xff0c;如果有取不到的&#xff0c;显然就无解。否则再看…

历久弥新 - 微软万亿市值背后的文化支撑(下)|DevOps案例研究

内容来源&#xff1a;DevOps案例深度研究-Microsoft文化支撑研究战队&#xff08;本文只展示部分PPT研究成果&#xff0c;更多细节请关注案例分享会&#xff0c;及本公众号。&#xff09;本案例内容贡献者&#xff1a;陈飞&#xff08;Topic Leader&#xff09;、陈雨卿、郭子奇…

Codeforces Round #579 (Div. 3) F2. Complete the Projects (hard version) dp + 贪心

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 排序方式跟easyeasyeasy版本的一样&#xff0c;但是hardhardhard版本是输出最多能选多少&#xff0c;所以我们对b<0b<0b<0的情况不能直接贪心的来选了&#xff0c;考虑用dpdpdp来…

D:Ehab the Xorcist

或许更好的阅读体验 Ehab the Xorcist 思路 刚看时确实是一脸懵&#xff0c;最怕的就是这种构造题了&#xff0c;然后细想好像能写啊。 判断不可行的条件&#xff0c;只有两种情况&#xff1a; 一、v<uv < uv<u是一定不可能的&#xff0c;一串数的异或值一定小于…

架构杂谈《九》

微服务与轻量级通信机制微服务架构是一种架构模式&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间胡亮协调、互相配合&#xff0c;为用户提供最终价值。在微服务架构中&#xff0c;服务与服务之间通信时&#xff0c;通常是通过轻量级的通信机制&#…

Codeforces Round #585 (Div. 2) E. Marbles 状压dp + 逆序对

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 考虑数列最终的状态一定是相同颜色在一起&#xff0c;所以我们发现他的颜色是有顺序的&#xff01;显然可以用状压dpdpdp来枚举颜色的顺序&#xff0c;但是又有问题了&#xff0c;你怎么确…

E:Sleeping Schedule(DP)

或许更好的阅读体验 Sleeping Schedule 思路 这道题读题就感觉像时DPDPDP&#xff0c;读完题后更加坚定了&#xff0c;这是一道DPDPDP题目。 我们考虑状态转移方程&#xff0c;dp[i][j]dp[i][j]dp[i][j]表示在第iii次入睡时间是jjj的时候的时间最优值&#xff0c;所以显然有…

GitLab CI 自动部署netcore web api 到Docker

前端篇文章中&#xff0c;我们已经成功的将asp.net core webapi在Docker容器中运行&#xff0c;并且部署了一套自己的GitLab环境。.Net & Docker&#xff08;二&#xff09;5分钟快速用Docker部署你自己的GitLab.Net & Docker&#xff08;一&#xff09;在Docker容器上…

Codeforces Round #585 (Div. 2) F. Radio Stations 2-sat + 神仙建模

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 你现在有ppp种电台&#xff0c;有nnn对关系(x,y)(x,y)(x,y)代表xxx电台或yyy电台中至少有一个&#xff0c;mmm对关系(x,y)(x,y)(x,y)代表xxx电台或yyy电台中最多有一个&#xff0c;每个电台有两个参数li,ril…

C. Orac and LCM(数论lcm, gcd)

C. Orac and LCM 思路 题目非常简单&#xff0c;就是求gcd(lcm(i,j))foriinrange(n),forjinrange(n),i<jgcd(lcm_(i,\ j))\ for\ i\ in\ range(n),\ for\ j\ in\ range(n),\ i\ <\ jgcd(lcm(​i, j)) for i in range(n), for j in range(n), i < j 对于包含a1a_1a1…

.net core 实现基于 cron 表达式的任务调度

.net core 实现基于 cron 表达式的任务调度Intro上次我们实现了一个简单的基于 Timer 的定时任务&#xff0c;详细信息可以看这篇文章 。但是使用过程中慢慢发现这种方式可能并不太合适&#xff0c;有些任务可能只希望在某个时间段内执行&#xff0c;只使用 timer 就显得不是那…

P6378 [PA2010] Riddle 2-sat + 前缀和优化建图

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你nnn个点mmm调变的无向图被分成kkk个部分&#xff0c;每个部分包含若干点&#xff0c;请选择一些关键点&#xff0c;使得每个部分恰好有一个关键点&#xff0c;且每条边至少有一个是关键点。 1≤k,w≤n≤…

E:Tree Queries(假树链剖分写法)

博客园地址 E&#xff1a;Tree Queries 思路 当我写完A完这道题后&#xff0c;百度了一下&#xff0c;发现好像没有人是用类树链剖分来写的&#xff0c;都是LCALCALCA&#xff0c;于是我就来水一篇树链剖分题解了。 第一步&#xff1a;贪心取点 我们可以发现&#xff0c;要…

ASP.NET Core Identity自定义数据库结构和完全使用Dapper而非EntityFramework Core

前言原本本节内容是不存在的&#xff0c;出于有几个人问到了我&#xff1a;我想使用ASP.NET Core Identity&#xff0c;但是我又不想使用默认生成的数据库表&#xff0c;想自定义一套&#xff0c;我想要使用ASP. NE Core Identity又不想使用EntityFramework Core。真难伺候&…