内存对齐究竟在做什么?

刚开始接触内存对齐的时候,我的理解是这样的——操作系统在以空间换时间。现在看看当时真是too young!实际,内存对齐的意义远比这大得多。

如果不理解内存对齐,我们所编写的程序将有可能产生下面的问题:

1、程序运行速度变慢;
2、应用程序产生死锁;
3、操作系统崩溃;
4、程序会毫无征兆的出错,产生silently fail !!!!

什么是内存对齐

“程序员必须要知道内存不是一个一个字节读取的”。

在了解什么是内存对齐之前,我们先来复习一下8086中的规则字和非规则字。

规则字是低位地址为奇数,否者是非规则字,非规则字读取需要两个周期,而规则字只需要一个周期。

这和8086的设计有关,如图所示,在8086中,mv AX,[1000],表示要读取从地址1000开始的一个字,因此BHE引脚输出低电平,表示有效,A0引脚输出低电平,相应的奇地址存储体(由BHE引脚选择)和偶地址存储体(由A0引脚选择)都被选中,因此传输16位数据。又如mv AL,[1000],表示要读取从1000开始的一个字节,因此BHE引脚输出高电平,A0引脚输出低电平,偶地址存储体被选中,传输一个字节。又如mv AL,[1001],表示要读取从1001开始的一个字节,因此BHE引脚输出低电平,A0引脚输出高电平,奇地址存储体被选中,传输一个字节。
在这里插入图片描述
读取一个字时,如果该字从偶地址开始存放,那么cpu只需要访问一次存储器即可。然而,如果该字从奇地址开始存放,cpu需要先访问奇地址存储体,把字的低字节取出,再访问偶地址存储体,把字的高字节取出,即访问了两次,降低了访问速度。因此,在汇编语言编程时,尽量把字存放在偶地址处,这就是规则字

参考8086的规则字,我们再来理解一下对齐的意思:当前内存单元的字节最低位地址恰巧就是我存取的那个字的。
视线转回CPU这里。我们假设一个CPU的总线宽度为32,那么显然它可以一次性取出四个字节的数据。但是要存取的字的地址只有一个,如果字的地址和当前最低位的地址是一样的,那就是对齐,反之,就不是对齐的。在对齐的内存地址上,32位的处理器可以一次性的将4个字节全部读出;而在非对齐的内存地址上,读取次数将加倍,再将不同周期读取的数据进行拼接。一个经典的例子是:
在这里插入图片描述
如果我们的数据存于内存的2-5中,在读取时实际上是先读取0-3,再读取4-7字节,再分别将2-3字节和4-5字节合并,最后得到所需的四字节数据。

但pc上的内存对齐并非这么简单,规则字问题并不是内存对齐的全部原因实际上,访问非对齐内存并没有我们想象的那么“简单”,内存实际上是有多个内存芯片共同组成的,为了提高访存的带宽,通常的做法是将地址分开,放到不同的芯片上,比如,第0-7bit在芯片0上储存,8-15bit在芯片2上组成,以此类推。这意味内存实际上并不是完全以byte形式组织的,而是以偏移量(offset)来给出具体地址的
这样,当采用对齐的地址访问时,比如从0x00开始访问四字节,显然四个字节储存于4个芯片,而且他们都有同样的偏移量(offset),这时我们就能一次获得所需的数据。但是,当从0x01开始读取4字节呢?此时前三个字节也是按顺序分别储存在1-3芯片中的,而且偏移量都是0,但是第四个字节却储存在偏移量为1的芯片0中。这意味着需要更多的io操作取读取地址。

这里我想说的是:

在访问内存时,CPU需要给出偏移量offset,而发送偏移量的总线只有一个。

这意味着在一次内存访问周期内CPU只能读取一个结果。当然,要想一次读取两个offset的内容也不是不能实现,你可以增加用于发送地址的bus数量。

对于一个64位的cpu,如果你希望在一个访问周期内读取未对齐的内存,你需要增加到8根总线。这意味着需要增加接近300个io。而通常cpu的管脚数量在700-2000之间,在这基础之上增加300将会是一个很大的改动。换句话说,就是会大大增加硬件的复杂程度。
同时,内存访问信号的频率是非常高的,增加的总线也会造成额外的噪声干扰。

当然,还有一种方法。由于非对齐访问最多也就访问两个不同的offset,而且这两个offset总是连续,我们可以再给内存内部加一根额外的线,这样就可以同时返回offset和offset+1两个偏移量上的数据了。但是,这样意味着芯片内多了一些额外的加法器(用于给offset加一,得到下一个偏移量),所有的读操作都会在读取前增加一个计算操作。
这一步会降低内存的时钟。于是乎,我们可能为了千分之一概率出现的非对齐访问,增加了99.9%的对齐访问的访问延时。显然这并不是一个明智的选择。

此外,访问非对齐的数据还存在一个问题:cache。通常来说,cache是和offset相关联的,不同的offset被不同的cache line缓存,因此,访问非对齐的数据也意味着多次的cache读取,同样会降低效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值