netcore高级知识点,内存对齐,原理与示例

2024-09-02 07:36

本文主要是介绍netcore高级知识点,内存对齐,原理与示例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近几年一直从事物联网开发,与硬件打交道越来越多,发现越接近底层开发对性能的追求越高,毕竟硬件资源相对上层应用来实在是太缺乏了。今天想和大家一起分享关于C#中的内存对齐,希望通过理解和优化内存对齐,可以帮助大家更好的提高程序性能以及资源利用效率。

01什么是内存对齐

内存对齐指把数据存储在内存中时,需要按照某种特定规则进行存储,使其内存存储在符合特定边界要求的内存地址上。而内存对齐主要目的则是减少CPU内存操作次数,提高内存操作效率,并提升CPU缓存命中率,从而提升整体性能。

02内存对齐原则

内存对齐原则包含两部分:内存对齐边界和内存对齐规则。

① 内存对齐边界:数据存储在内存中的起始内存地址必须满足条件。例如,8字节对齐则要求数据的起始内存地址必须是8的倍数;
② 内存对齐规则:不同的硬件平台内存对齐规则也各有差异,比如:x86、x64架构在内存对齐方面比较宽松,而ARM、RISC-V架构则相对比较严格;一般32位处理器要求4字节对齐,而64位处理器要求8字节对齐;

因此不同的CPU架构和平台则内存对齐规则也各有不同,而这些差异也都是为了使数据在内存中的布局更加符合CPU操作方式,从而提高程序执行效率。

03C#中的内存对齐

1、“托管代码”和“非托管代码”

托管代码:执行过程交给运行时CLR管理的代码,运行时CLR负责提取托管代码并编译成机器代码最后执行,同时运行时CLR还负责自动内存管理、安全边界和类型安全等重要服务。

“非托管代码”:即不被运行时CLR管理的代码,比如运行C/C++语言编写的代码,而此时开发任意就需要亲自处理很多事情,比如内存管理、垃圾回收、安全问题等等。

因此一般对于托管代码来说,内存的分配以及对齐策略都被运行时CLR一手包办了,无需我们过多关注,而如果需要通过P/Invoke和COM互操作来调用非托管代码则需要开发者自己处理内存对齐策略了。

当然也不是说纯托管代码就没有对内存对齐操作空间了,只是相对来说与非托管代码交互时使用内存对齐操作空间更大。

2、StructLayoutAttribute特性

无论托管内存还是非托管内存,都可以用StructLayoutAttribute特性来对其进行内存布局控制,简单来说对于托管代码可以使用LayoutKind枚举值Explicit进行显示控制,而对于非托管代码LayoutKind枚举值都可以控制。

04示例-字段顺序影响内存占用大小

我们用StructLayout(LayoutKind.Sequential标记OriginalLayout结构体,看看每个字段的布局情况及其与占用内存总大小之间的关系,先来看下面一段代码:

using System.Runtime.InteropServices;
namespace CSharp
{public class MemoryLayout{[StructLayout(LayoutKind.Sequential)]public struct OriginalLayout{public long LongField1;public short ShortField;public byte ByteField1;}public static void Run(){Console.WriteLine($"OriginalLayout LongField1 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "LongField1")} ");Console.WriteLine($"OriginalLayout ShortField 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "ShortField")} ");Console.WriteLine($"OriginalLayout ByteField1 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "ByteField1")} ");Console.WriteLine($"OriginalLayout 总大小: {Marshal.SizeOf(typeof(OriginalLayout))} bytes");Console.ReadKey();}}
}

我们使用Marshal.OffsetOf计算每个字段偏移量,即第一个字段偏移量表示其内存地址为0,则第二个字段偏移量表示为其相对第一个字段内存地址值的相对值,使用Marshal.SizeOf计算类型所占内存总大小。
如下图是上面代码运行结果:
在这里插入图片描述
首先说下long类型为8字节、short类型为2字节、byte类型为1字节,再来详细说下每个值怎么来的。

首先因为LongField1是第一个字段所以为0,并且因为long类型为8字节,所以LongField1使用了0-7内存地址段,所有第二个字段ShortField偏移量为8,因此ShortField使用了8-9内存地址段,所以第三个字段ByteField1偏移量为10。

那为什么总大小不是8+2+1=11字节,而16字节呢?这是因为对于类型的对齐方式默认会以其最大的元素对齐方式为准,并且整个类型大小是最大元素大小的整数倍,因此这里的总大小是8的倍数,因为2+1并没有占满8字节,因此ByteField1后面被自动填充了5个字节,以此达到对齐要求。所以最后就是8+2+1+5(自动填充)=16字节。

然后我们把LongField1和ShortField两个字段调整一下位置,再来看看运行结果:

public class MemoryLayout
{[StructLayout(LayoutKind.Sequential)]public struct OriginalLayout{public short ShortField;public long LongField1;public byte ByteField1;}public static void Run(){Console.WriteLine($"OriginalLayout ShortField 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "ShortField")} ");Console.WriteLine($"OriginalLayout LongField1 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "LongField1")} ");Console.WriteLine($"OriginalLayout ByteField1 偏移量: {Marshal.OffsetOf(typeof(OriginalLayout), "ByteField1")} ");Console.WriteLine($"OriginalLayout 总大小: {Marshal.SizeOf(typeof(OriginalLayout))} bytes");Console.ReadKey();}
}

在这里插入图片描述

这里为什么又是24字节呢?

首先虽然ShortField只占了2字节,使用了0-1内存地址段,但是LongField1并不能从2内存地址值开始排版,因为每个字段必须与其自身大小的字段或类型的对齐方式对齐,也就是说LongField1占8字节,那么其内存地址起始值也要是8的整数倍,因此LongFiled1使用了8-15内存地址段,而ShortField和LongFiled1之间会被自动填充6个字节,同样的ByteField1后面也被自动填充7个字节,因此总大小为24字节。

这里只是举了个小例子来展示字段顺序不同,对最终类型所占内存总大小的,这也给我们设计低内存消耗程序设计提供了空间。

当然这里只是简单使用了StructLayout,还Pack属性,以及Explicit和FieldOffset,还有CharSet、MarshalAs等复杂的功能都没有介绍,有兴趣的可以深入研究研究。本文只是简单内存对齐的原理原则以及简单的内存优化,后面有机会再给大家深入介绍。

这篇关于netcore高级知识点,内存对齐,原理与示例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Redis中Hash从使用过程到原理说明

《Redis中Hash从使用过程到原理说明》RedisHash结构用于存储字段-值对,适合对象数据,支持HSET、HGET等命令,采用ziplist或hashtable编码,通过渐进式rehash优化... 目录一、开篇:Hash就像超市的货架二、Hash的基本使用1. 常用命令示例2. Java操作示例三

Redis中Set结构使用过程与原理说明

《Redis中Set结构使用过程与原理说明》本文解析了RedisSet数据结构,涵盖其基本操作(如添加、查找)、集合运算(交并差)、底层实现(intset与hashtable自动切换机制)、典型应用场... 目录开篇:从购物车到Redis Set一、Redis Set的基本操作1.1 编程常用命令1.2 集

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的