c# 结构体 4字节对齐_深入浅出C#结构体——封装以太网心跳包的结构为例

目录

  • 1.应用背景

  • 2.结构体解析

    • 2.1.结构体存在栈中

    • 2.2.结构体不需要手动释放

  • 3.封装心跳包结构体

  • 4.结构体静态帮助类

  • 5.New出来的结构体是存在堆中还是栈中?

    • 5.1.不带形参的结构体构造

    • 5.2.带形参的结构体构造

  • 6.性能测试

  • 7.原因分析

  • 8.下一期:结构体与类封装的心跳包性能对比测试

  • 9.IL工具使用分享

1.应用背景

底端设备有大量网络报文(字节数组):心跳报文,数据采集报文,告警报文上报。需要有对应的报文结构去解析这些字节流数据。

2.结构体解析

由此,我第一点就想到了用结构体去解析。原因有以下两点:

2.1.结构体存在栈中

类属于引用类型,存在堆中;结构体属于值类型,存在栈中,在一个对象的主要成员为数据且数据量不大的情况下,使用结构会带来更好的性能。

2.2.结构体不需要手动释放

属于托管资源,系统自动管理生命周期,局部方法调用完会自动释放,全局方法会一直存在。

3.封装心跳包结构体

心跳协议报文如下:

d59b6f4762a3927a9c50b759403633ad.png

对应结构体封装如下:

StructLayout(LayoutKind.Sequential, Pack = 1)] 

4.结构体静态帮助类

主要实现了字节数组向结构体转换方法,以及结构体向字节数组的转换方法。

public 

5.New出来的结构体是存在堆中还是栈中?

有同事说new出来的都会放在堆里,我半信半疑。怎么去确定,new出来的结构体到底放在哪里有两种方式,一种是使用Visual Studio的调试工具查看,这种方法找了好久没找到怎么去查看,路过的高手烦请指点下;第二种方法就是查看反编译dll的IL(Intermediate Language)语言。查看最终是以怎样的方式去实现的。不懂IL想了解IL的可以看此篇文章

5.1.不带形参的结构体构造

  • 调用代码

//初始化结构体

4cc097c7181cafdaca58ab809251f6f4.png
从对应的IL代码可以看出只是initobj,并没有newobj,其中newobj表示分配内存,完成对象初始化;而initobj表示对值类型的初始化。

  • newobj用于分配和初始化对象;而initobj用于初始化值类型。因此,可以说,newobj在堆中分配内存,并完成初始化;而initobj则是对栈上已经分配好的内存,进行初始化即可,因此值类型在编译期已经在栈上分配好了内存。

  • newobj在初始化过程中会调用构造函数;而initobj不会调用构造函数,而是直接对实例置空。

  • newobj有内存分配的过程;而initobj则只完成数据初始化操作。

initobj 的执行结果是,将tcpHeartPacket中的引用类型初时化为null,而基元类型则置为0。
综上,new 结构体(无参情况)是放在栈中的,只是做了null/0初始化。

5.2.带形参的结构体构造

接下来看下带形参的结构体存放位置。
简化版带形参的结构体如下:

public 

调用如下:

//带形参结构体new初始化

IL代码如下:2db242f3b823236190e88bf512e77fa1.png

形成了鲜明的对比,new带参的结构体。IL只是去call(调用)ctor(结构体的构造函数),而下面的new类则直接就是newobj,实例化了一个对象存到堆空间去了。

综合5.1,5.2表明结构体的new确实是存在栈里的,而类的new是存在堆里的。

6.性能测试

测试结果如下:

93a69ae4b3d1ba1be3641e689679d31c.pnge96fa96ef18807a515489021d6bf8954.png

使用结构体解析包需要几十个微妙,其实效率还是很差的。我用类封装成包,解析了,只需要几个微妙,性能差5到10倍。

7.原因分析

主要时间消耗在了BytesToStuct方法,代码详见4

  • 心跳包里面用了很多byte[]字节数组,而字节数组本身需要在堆里开辟空间;

  • 该方法进行了装箱拆箱操作;

  • 分配内存在堆上,还是在堆上进行了copy操作;
    拆装箱的IL代码如下:

4b1645aa745cfba13e82b1883c8bc963.png

装箱使用的box指令,取消装箱是 unbox.any 指令

8.下一期:结构体与类封装的心跳包性能对比测试

当数据比较大的时候,结构体这种数据复制机制会带来较大的开销。也难怪微软给出的准则中有一条:“当类型定义大于16字节时不要选用struct”。最终我也选择了类来封装以太网包的解析,性能可以达到微妙级,会在下一篇文章《结构体与类封装的心跳包性能对比测试》中作详细描述。

9.IL工具使用分享

  • 使用ildasm工具
    VS2013外部工具中添加ildasm.exe:

    https://blog.csdn.net/jackson0714/article/details/44627161

  • 使用dnSpy工具
    dnSpy的github地址:https://github.com/cnxy/dnSpy/


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://www.cnblogs.com/JerryMouseLi/p/12606920.html

版权申明:本文来源于网友收集或网友提供,如果有侵权,请转告版主或者留言,本公众号立即删除。

98186af5744edefdb60d71cbe045720a.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值