用 Span 对 C# 进程中三大内存区域进行统一访问 ,太厉害了!

一:背景

1. 讲故事

前段时间写了几篇 C# 漫文,评论留言中有很多朋友多次提到 Span,周末抽空看了下,确实是一个非常????????的新结构,让我想到了当年的WCF,它统一了.NET下各种零散的分布式技术,包括:.NET Remoteing,WebService,NamedPipe,MSMQ,而这里的 Span 统一了 C# 进程中的三大块内存访问,包括:栈内存, 托管堆内存, 非托管堆内存,画个图如下:

接下来就和大家具体聊聊这三大块的内存统一访问。

二:进程中的三大块内存解析

1. 栈内存

大家应该知道方法内的局部变量是存放在栈上的,而且每一个线程默认会被分配 1M 的内存空间,我举个例子:

static void Main(string[] args){int i = 10;long j = 20;List<string> list = new List<string>();}

上面 i,j 的值都是存于栈上,list的堆上内存地址也是存于栈上,为了看个究竟,可以用 windbg 验证一下:


0:000> !clrstack -l
OS Thread Id: 0x2708 (0)Child SP               IP Call Site
00000072E47CE558 00007ff89cf7c184 [InlinedCallFrame: 00000072e47ce558] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000072E47CE558 00007ff7c7c03fd8 [InlinedCallFrame: 00000072e47ce558] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000072E47CE520 00007FF7C7C03FD8 ILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
00000072E47CE7B0 00007FF8541E530D System.Console.ReadLine()
00000072E47CE7E0 00007FF7C7C0101E DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 22]LOCALS:0x00000072E47CE82C = 0x000000000000000a0x00000072E47CE820 = 0x00000000000000140x00000072E47CE818 = 0x0000018015aeab10

通过 clrstack -l 查看线程栈,最后三行可以明显的看到 0a -> 10, 14 -> 20 , 0xxxxxxb10 => list堆地址,除了这些简单类型,还可以在栈上分配复杂类型,这里就要用到 stackalloc 关键词, 如下代码:

int* ptr = stackalloc int[3] { 10, 11, 12 };

问题就在这里,指针类型虽然灵活,但是做任何事情都比较繁琐,比如说:

  • 查找某一个数是否在 int[] 中

  • 反转 int[]

  • 剔除尾部的某一个数字(比如 12)

就拿第一个问题来说,操作指针的代码如下:

//指针接收int* ptr = stackalloc int[3] { 10, 11, 12 };//包含判断for (int i = 0; i < 3; i++){if (*ptr++ == 11){Console.WriteLine(" 11 存在 数组中");}}

后面的两个问题就更加复杂了,既然 Span 是统一访问,就应该用 Span 来接 stackalloc,代码如下:

Span<int> span = stackalloc int[3] { 10, 11, 12 };//1. 是否包含var hasNum = span.Contains(11);//2. 反转span.Reverse();//3. 剔除尾部span.Trim(12);

这就很????????了,你既不需要接触指针,又能完成指针的大部分操作,而且还特别便捷,佩服,最后来验证一下 int[] 是否真的在 线程栈 上。


0:000> !clrstack -l
000000ED7737E4B0 00007FF7C4EA16AD DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 28]LOCALS:0x000000ED7737E570 = 0x000000ed7737e4d00x000000ED7737E56C = 0x00000000000000010x000000ED7737E558 = 0x000000ed7737e4d00:000> dp 0x000000ed7737e4d0
000000ed`7737e4d0  0000000b`0000000c 00000000`0000000a

从 Locals 处的 0x000000ED7737E570 = 0x000000ed7737e4d0 可以看到 key / value 是非常相近的,说明在栈上无疑。

从最后一行 a,b,c 可看出对应的就是数组中的 10,11,12。

2. 非托管堆内存

说到非托管内存,让我想起了当年 C# 调用 C++ 的场景,代码到处充斥着类似下面的语句:

private bool SendMessage(int messageType, string ip, string port, int length, byte[] messageBytes){bool result = false;if (windowHandle != 0){var bytes = new byte[Const.MaxLengthOfBuffer];Array.Copy(messageBytes, bytes, messageBytes.Length);int sizeOfType = Marshal.SizeOf(typeof(StClientData));StClientData stData = new StClientData{Ip = GlobalConvert.IpAddressToUInt32(IPAddress.Parse(ip)),Port = Convert.ToInt16(port),Length = Convert.ToUInt32(length),Buffer = bytes};int sizeOfStData = Marshal.SizeOf(stData);IntPtr pointer = Marshal.AllocHGlobal(sizeOfStData);Marshal.StructureToPtr(stData, pointer, true);CopyData copyData = new CopyData{DwData = (IntPtr)messageType,CbData = Marshal.SizeOf(sizeOfType),LpData = pointer};SendMessage(windowHandle, WmCopydata, 0, ref copyData);Marshal.FreeHGlobal(pointer);string data = GlobalConvert.ByteArrayToHexString(messageBytes);CommunicationManager.Instance.SendDebugInfo(new DataSendEventArgs() { Data = data });result = true;}return result;}

上面代码中的: IntPtr pointer = Marshal.AllocHGlobal(sizeOfStData);Marshal.FreeHGlobal(pointer) 就用到了非托管内存,从现在开始你就可以用 Span 来接 Marshal.AllocHGlobal 分配的非托管内存啦!????????‍????,如下代码所示:

class Program{static unsafe void Main(string[] args){var ptr = Marshal.AllocHGlobal(3);//将 ptr 转换为 spanvar span = new Span<byte>((byte*)ptr, 3) { [0] = 10, [1] = 11, [2] = 12 };//然后在  span 中可以进行各种操作了。。。Marshal.FreeHGlobal(ptr);}}

这里我也用 windbg 给大家看一下 未托管内存 在内存中是个什么样子。


0:000> !clrstack -l
OS Thread Id: 0x3b10 (0)Child SP               IP Call Site
000000A51777E758 00007ff89cf7c184 [InlinedCallFrame: 000000a51777e758] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
000000A51777E758 00007ff7c4654dd8 [InlinedCallFrame: 000000a51777e758] Interop+Kernel32.ReadFile(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
000000A51777E720 00007FF7C4654DD8 ILStubClass.IL_STUB_PInvoke(IntPtr, Byte*, Int32, Int32 ByRef, IntPtr)
000000A51777E9E0 00007FF7C46511D0 DataStruct.Program.Main(System.String[]) [E:\net5\ConsoleApp2\ConsoleApp1\Program.cs @ 26]LOCALS:0x000000A51777EA58 = 0x00000274901447600x000000A51777EA48 = 0x00000274901447600x000000A51777EA38 = 0x00000274901447600:000> dp 0x0000027490144760
00000274`90144760  abababab`ab0c0b0a abababab`abababab        

最后一行的 0c0b0a  这就是低位到高位的 10,11,12 三个数,接下来从 Locals 处 0x000000A51777EA58 = 0x0000027490144760 可以看出,这个key,value 相隔十万八千里,说明肯定不在栈内存中,继续用 windbg 鉴别一下 0x0000027490144760 是否是托管堆上,可以用 !eeheap -gc 查看托管堆地址范围,如下代码:


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000274901B1030
generation 1 starts at 0x00000274901B1018
generation 2 starts at 0x00000274901B1000
ephemeral segment allocation context: nonesegment             begin         allocated              size
00000274901B0000  00000274901B1000  00000274901C5370  0x14370(82800)
Large object heap starts at 0x00000274A01B1000segment             begin         allocated              size
00000274A01B0000  00000274A01B1000  00000274A01B5480  0x4480(17536)
Total Size:              Size: 0x187f0 (100336) bytes.
------------------------------
GC Heap Size:    Size: 0x187f0 (100336) bytes.

从上面信息可以看到,0x0000027490144760 明显不在:3代堆:00000274901B1000 ~ 00000274901C5370 和 大对象堆:00000274A01B1000 ~ 00000274A01B5480 区间范围内。

3. 托管堆内存

用 Span 统一托管内存访问那是相当简单了,如下代码所示:

Span<byte> span = new byte[3] { 10, 11, 12 };

同样,你有了Span,你就可以使用 Span 自带的各种方法,这里就不多介绍了,大家有兴趣可以实操一下。

三:总结

总的来说,这一篇主要是从思想上带大家一起认识 Span,以及如何用 Span 对接 三大区域内存,关于 Span 的好处以及源码解析,后面上专门的文章吧!

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

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

相关文章

福利 | 全网疯传免费领,一整套算法课程,拿走不谢!

算法对于程序员而言&#xff0c;到底有多重要&#xff0c;这已经是一个老生常谈的话题了。我看过很多有潜力且非常努力的程序员&#xff0c;在进阶高级工程师或架构师的路上&#xff0c;栽在了“算法”上&#xff0c;说实话&#xff0c;我并不意外。如果说得功利和实际一些&…

软件构造学习笔记-第十二周

本周介绍了异常以及异常的处理。Java的异常机制将处理异常的代码整合到一个代码块中&#xff0c;而不是通过if-else进行处理&#xff0c;提高了代码整体的简洁性。同时&#xff0c;异常机制可以更加详细地说明错误类型、引起错误的位置、错误信息&#xff0c;对程序健壮性十分有…

C#中LINQ与数据管道

假如有一个集合&#xff0c;里面有数字1-10&#xff0c;现在想实现从这10个数字中取出偶数数字&#xff0c;然后将得到的偶数乘以10&#xff0c;最后输出前三个。代码如下&#xff1a;代码非常的简单&#xff0c;最后会得到20、40、60这三个数字&#xff0c;但结果并不是我们今…

软件构造学习笔记-实验4

磕磕绊绊也算是完成了最后一个实验。记录一下实验中遇到的问题。 IDEA中SpotBugs的安装和使用 从网上找教程&#xff0c;都说IDEA没有使用版权。结果我直接在插件中就找到了。点击安装并重启即可。 使用时&#xff0c;选择需要操作的项目/包/类点击右键&#xff0c;就能看到S…

使用ML.NET模型生成器来完成图片性别识别

什么是ML.NET&#xff1f;ML.NET 使你能够在联机或脱机场景中将机器学习添加到 .NET 应用程序中。借助此功能&#xff0c;可以使用应用程序的可用数据进行自动预测。机器学习应用程序利用数据中的模式来进行预测&#xff0c;而不需要进行显式编程。ML.NET 的核心是机器学习模型…

软件构造学习笔记-第十四周、十五周

课程进入了尾声。本周内容主要是线程安全相关。线程错误比一般的错误更加难以发现和修改&#xff0c;甚至加入一条print语句就能改变时间分片&#xff0c;从而导致错误消失。重点介绍了“锁”的机制&#xff0c;在使用时避免对整个方法进行lock&#xff0c;而是对可能发生线程不…

打爆你的 CPU

通过一段代码打爆你的 CPUIntro这是这个煞笔代码系列的最后一篇——Full CPU&#xff0c;相对来说也比前面三个简单一些&#xff0c;没有那么多的知识点。今天来尝试写一段代码&#xff0c;把 CPU 打满&#xff0c;让所有处理器的 CPU 使用率达到 100%&#xff0c;Lets do it.如…

吴恩达DeepLearningCourse1-神经网络和深度学习

计划在9月4日&#xff08;截止日期&#xff09;之前完成DeepLearning的所有课程学习。每个课程对应一篇博客&#xff0c;根据学习进度随时更新。 参考课程文章目录&#xff08;一&#xff09;深度学习概论结构化数据和非结构化数据提高效果的方法&#xff08;二&#xff09;神经…

吴恩达DeepLearningCourse2-改善深层神经网络:超参数调试、正则化以及优化

文章目录第一周&#xff1a;深度学习的实用层面训练、开发、测试集偏差、方差机器学习基本步骤L2正则化Dropout&#xff08;随机失活&#xff09;正则化其它正则化方法正则化输入神经网络的权重初始化梯度检验第二周&#xff1a;优化算法Mini-Batch梯度下降法指数加权平均指数加…

面向监狱编程:一伙人植入木马程序至 559 万部手机,盈利 33 万!

2018 年下半年以来&#xff0c;孙某&#xff08;上海宏路数据技术股份有限公司副总经理&#xff09;与苏某&#xff08;北京亿量科技有限公司法定代表人&#xff09;商议合作开展锁屏拉起广告业务&#xff08;通过在用户手机上植入带有木马程序的 SDK&#xff0c;可控制手机在锁…

吴恩达DeepLearningCourse3-结构化机器学习项目

文章目录第一周&#xff1a;机器学习策略1正交化单一数字评估指标满足和优化指标训练/开发/测试集机器学习和人的表现第二周&#xff1a;机器学习策略2进行误差分析修正标注错误的数据使用来自不同分布的数据进行训练和测试数据分布不匹配时的偏差与方差的分析处理数据分布不匹…

程序员过关斩将--搞定秒杀,只需要这几步!!

“灵魂拷问秒杀这种大并发的写场景&#xff0c;直接分库分表开干&#xff1f;应对秒杀活动的流量高峰很难吗&#xff1f;不要拿淘宝级别的秒杀忽悠我秒杀活动特点我敢说凡是做过电商的同学&#xff0c;都会遇到运营展开的秒杀&#xff0c;限时购等“高并发”的活动。市面上也有…

吴恩达DeepLearningCourse4-卷积神经网络

部分内容参考之前的笔记 PyTorch深度学习实践 文章目录第一周&#xff1a;卷积神经网络边缘检测Padding、Stride三维卷积卷积神经网络中的一层池化层第二周&#xff1a;深度卷积网络实例探究残差网络1x1卷积Inception模块和网络卷积神经网络的迁移学习第三周&#xff1a;目标检…

那些鼓吹国内首个.NET 5框架的,该醒醒了!

前两天看过园子里有篇【国内首个 .NET 5 框架 XX 斩获 XXX stars&#xff0c;XXX 发布】&#xff0c;一顿羡慕嫉妒恨啊。我这.net core 3.1才上手没几天,还没用热乎呢&#xff0c;你这.NET 5的框架都出来了。我好难啊&#xff01;不过难归难咱也得跟上啊。于是一个天高云淡的的…

吴恩达DeepLearningCourse5-序列模型

终于在八月末学完了这门课程&#xff0c;这个月虽然为此不停地忙碌&#xff0c;但每天都在进步也是一种乐趣。 吴恩达教授的课程循序渐进&#xff0c;适合初学者&#xff0c;非常感谢他的辛苦付出。 文章目录第一周&#xff1a;循环序列模型循环神经网络&#xff08;RNN&#…

IdentityServer4系列 | 客户端凭证模式

一、前言从上一篇关于 快速搭建简易项目中&#xff0c;通过手动或者官方模板的方式简易的实现了我们的IdentityServer授权服务器搭建&#xff0c;并做了相应的配置和UI配置&#xff0c;实现了获取Token方式。而其中我们也注意到了三点就是&#xff0c;有哪些用户(users)可以通过…

数据结构 - 链表

准备重启尘封一年的博客作为学习笔记&#xff0c;看看自己能坚持多久。 最近会记录做过的算法题&#xff0c;语言描述只用于会意&#xff0c;仅供参考。 文章目录0.从尾到头获取链表的值&#xff08;不是反转链表&#xff09;1.寻找/删除单链表倒数第k个节点3.寻找单链表的中点…

[读书笔记] 《修炼之道:.NET 开发要点精讲》

《修炼之道:.NET 开发要点精讲》目录《修炼之道:.NET 开发要点精讲》第 1 章 另辟蹊径&#xff1a;解读.NET1.7 本章思考 > 位置 465第 2 章 高屋建瓴&#xff1a;梳理编程约定2.2 方法与线程的关系 > 位置 5192.7 线程安全 > 位置 5952.8 调用与回调 > 位置 6612.…

ASP.NET Core 使用 gRPC 初探

&#xff08;RPC通讯示意图&#xff09;为什么突然说到gRPC呢&#xff0c;其实以前就想说一说这个东西&#xff0c;也想尝试使用一下&#xff0c;一直没有机会&#xff0c;一直看我公众号的小伙伴肯定都知道&#xff0c;这几天一直在录制一个《eShopOnContainer微服务架构》系列…

源码都没调试过,怎么能说熟悉 redis 呢?

一&#xff1a;背景 1. 讲故事记得在很久之前给初学的朋友们录制 redis 视频课程&#xff0c;当时结合了不少源码进行解读&#xff0c;自以为讲的还算可以&#xff0c;但还是有一个非常核心的点没被分享到&#xff0c;那就是源码级调试&#xff0c; 对&#xff0c;读源码还远远…