内存对齐(memory align)

0. 内存结构

我们平时所称的内存也叫随机访问存储器(random-access memory)也叫RAM。而RAM分为两类:

  • 一类是静态RAM(SRAM),这类SRAM用于前边介绍的CPU高速缓存L1Cache,L2Cache,L3Cache。其特点是访问速度快,访问速度为1 - 30个时钟周期,但是容量小,造价高。
  • 另一类则是动态RAM(DRAM),这类DRAM用于我们常说的主存上,其特点的是访问速度慢(相对高速缓存),访问速度为50 - 200个时钟周期,但是容量大,造价便宜些(相对高速缓存)。

内存由一个一个的存储器模块(memory module)组成,它们插在主板的扩展槽上。常见的存储器模块通常以64位为单位(8个字节)传输数据到存储控制器上或者从存储控制器传出数据。
在这里插入图片描述
如图所示内存条上黑色的元器件就是存储器模块(memory module)。多个存储器模块连接到存储控制器上,就聚合成了主存。
在这里插入图片描述
而前边介绍到的DRAM芯片就包装在存储器模块中,每个存储器模块中包含8个DRAM芯片,依次编号为0 - 7。
在这里插入图片描述
而每一个DRAM芯片的存储结构是一个二维矩阵,二维矩阵中存储的元素我们称为超单元(supercell),每个supercell大小为一个字节(8 bit)。每个supercell都由一个坐标地址(i,j)。

i表示二维矩阵中的行地址,在计算机中行地址称为RAS(row access strobe,行访问选通脉冲)。
j表示二维矩阵中的列地址,在计算机中列地址称为CAS(column access strobe,列访问选通脉冲)。

下图中的supercell的RAS = 2,CAS = 2。
在这里插入图片描述
DRAM芯片中的信息通过引脚流入流出DRAM芯片。每个引脚携带1 bit的信号。
图中DRAM芯片包含了两个地址引脚(addr),因为我们要通过RAS,CAS来定位要获取的supercell。还有8个数据引脚(data),因为DRAM芯片的IO单位为一个字节(8 bit),所以需要8个data引脚从DRAM芯片传入传出数据。

注意这里只是为了解释地址引脚和数据引脚的概念,实际硬件中的引脚数量是不一定的。

1. DRAM芯片的访问

我们现在就以读取上图中坐标地址为(2,2)的supercell为例,来说明访问DRAM芯片的过程。
在这里插入图片描述

  1. 首先存储控制器将行地址RAS = 2通过地址引脚发送给DRAM芯片。
  2. DRAM芯片根据RAS = 2将二维矩阵中的第二行的全部内容拷贝到内部行缓冲区中。
  3. 接下来存储控制器会通过地址引脚发送CAS = 2到DRAM芯片中。
  4. DRAM芯片从内部行缓冲区中根据CAS = 2拷贝出第二列的supercell并通过数据引脚发送给存储控制器。

DRAM芯片的IO单位为一个supercell,也就是一个字节(8 bit)。

2. CPU如何读写主存

前边我们介绍了内存的物理结构,以及如何访问内存中的DRAM芯片获取supercell中存储的数据(一个字节)。本小节我们来介绍下CPU是如何访问内存的。
在这里插入图片描述

2.1 总线结构

CPU与内存之间的数据交互是通过总线(bus)完成的,而数据在总线上的传送是通过一系列的步骤完成的,这些步骤称为总线事务(bus transaction)。

其中数据从内存传送到CPU称之为读事务(read transaction),数据从CPU传送到内存称之为写事务(write transaction)。

总线上传输的信号包括:地址信号,数据信号,控制信号。其中控制总线上传输的控制信号可以同步事务,并能够标识出当前正在被执行的事务信息:
当前这个事务是到内存的?还是到磁盘的?或者是到其他IO设备的?
这个事务是读还是写?
总线上传输的地址信号(内存地址),还是数据信号(数据)?

若使用MESI缓存一致性协议,当core0修改字段a的值时,其他CPU核心会在总线上嗅探字段a的内存地址,如果嗅探到总线上出现字段a的内存地址,说明有人在修改字段a,这样其他CPU核心就会失效自己缓存字段a所在的cache line。

如上图所示,其中系统总线是连接CPU与IO bridge的,存储总线是来连接IO bridge和主存的。
IO bridge负责将系统总线上的电子信号转换成存储总线上的电子信号。IO bridge也会将系统总线和存储总线连接到IO总线(磁盘等IO设备)上。这里我们看到IO bridge其实起的作用就是转换不同总线上的电子信号。

2.2 CPU从内存读取数据过程

假设CPU现在要将内存地址为A的内容加载到寄存器中进行运算。
在这里插入图片描述
首先CPU芯片中的总线接口会在总线上发起读事务(read transaction)。该读事务分为以下步骤进行:

  1. CPU将内存地址A放到系统总线上。随后IO bridge将信号传递到存储总线上。
  2. 主存感受到存储总线上的地址信号并通过存储控制器将存储总线上的内存地址A读取出来。
  3. 存储控制器通过内存地址A定位到具体的存储器模块,从DRAM芯片中取出内存地址A对应的数据X。
  4. 存储控制器将读取到的数据X放到存储总线上,随后IO bridge将存储总线上的数据信号转换为系统总线上的数据信号,然后继续沿着系统总线传递。
  5. CPU芯片感受到系统总线上的数据信号,将数据从系统总线上读取出来并拷贝到寄存器中。

以上就是CPU读取内存数据到寄存器中的完整过程。
但是其中还涉及到一个重要的过程,这里我们还是需要摊开来介绍一下,那就是存储控制器如何通过内存地址A从主存中读取出对应的数据X的?

接下来我们结合前边介绍的内存结构以及从DRAM芯片读取数据的过程,来总体介绍下如何从主存中读取数据。

2.3 如何根据内存地址从主存中读取数据

前边介绍到,当主存中的存储控制器感受到了存储总线上的地址信号时,会将内存地址从存储总线上读取出来。
随后会通过内存地址定位到具体的存储器模块。还记得内存结构中的存储器模块吗??
在这里插入图片描述
而每个存储器模块中包含了8个DRAM芯片,编号从0 - 7

存储控制器会将内存地址转换为DRAM芯片中supercell在二维矩阵中的坐标地址(RAS,CAS)。并将这个坐标地址发送给对应的存储器模块。随后存储器模块会将RAS和CAS广播到存储器模块中的所有DRAM芯片。依次通过(RAS,CAS)从DRAM0到DRAM7读取到相应的supercell
在这里插入图片描述
我们知道一个supercell存储了8 bit数据,这里我们从DRAM0到DRAM7 依次读取到了8个supercell也就是8个字节,然后将这8个字节返回给存储控制器,由存储控制器将数据放到存储总线上。

CPU总是以word size为单位从内存中读取数据,在64位处理器中的word size为8个字节。64位的内存也只能每次吞吐8个字节。

CPU每次会向内存读写一个cache line大小的数据(64个字节),但是内存一次只能吞吐8个字节。

所以在内存地址对应的存储器模块中,DRAM0芯片存储第一个低位字节(supercell),DRAM1芯片存储第二个字节,…依次类推DRAM7芯片存储最后一个高位字节。

内存一次读取和写入的单位是8个字节。而且在程序员眼里连续的内存地址实际上在物理上是不连续的。因为这连续的8个字节其实是存储于不同的DRAM芯片上的。每个DRAM芯片存储一个字节(supercell)。

在这里插入图片描述

3. 为什么要内存对齐

我们在了解了内存结构以及CPU读写内存的过程之后,现在我们回过头来讨论下本小节开头的问题:为什么要内存对齐?
下面笔者从三个方面来介绍下要进行内存对齐的原因:

  • 速度

CPU读取数据的单位是根据word size来的,在64位处理器中word size = 8字节,所以CPU向内存读写数据的单位为8字节。

在64位内存中,内存IO单位为8个字节,我们前边也提到内存结构中的存储器模块通常以64位为单位(8个字节)传输数据到存储控制器上或者从存储控制器传出数据。因为每次内存IO读取数据都是从数据所在具体的存储器模块中包含的这8个DRAM芯片中以相同的(RAM,CAS)依次读取一个字节,然后在存储控制器中聚合成8个字节返回给CPU。

由于存储器模块中这种由8个DRAM芯片组成的物理存储结构的限制,内存读取数据只能是按照地址顺序8个字节的依次读取----8个字节8个字节地来读取数据
在这里插入图片描述

  • 假设我们现在读取0x0000 - 0x0007这段连续内存地址上的8个字节。由于内存读取是按照8个字节为单位依次顺序读取的,而我们要读取的这段内存地址的起始地址是0(8的倍数),所以0x0000 - 0x0007中每个地址的坐标都是相同的(RAS,CAS)。所以他可以在8个DRAM芯片中通过相同的(RAS,CAS)一次性读取出来。
  • 如果我们现在读取0x0008 - 0x0015这段连续内存上的8个字节也是一样的,因为内存段起始地址为8(8的倍数),所以这段内存上的每个内存地址在DREAM芯片中的坐标地址(RAS,CAS)也是相同的,我们也可以一次性读取出来。
    注意:0x0000 - 0x0007内存段中的坐标地址(RAS,CAS)与0x0008 - 0x0015内存段中的坐标地址(RAS,CAS)是不相同的。
  • 但如果我们现在读取0x0007 - 0x0014这段连续内存上的8个字节情况就不一样了,由于起始地址0x0007在DRAM芯片中的(RAS,CAS)与后边地址0x0008 - 0x0014的(RAS,CAS)不相同,所以CPU只能先从0x0000 - 0x0007读取8个字节出来先放入结果寄存器中并左移7个字节(目的是只获取0x0007),然后CPU在从0x0008 - 0x0015读取8个字节出来放入临时寄存器中并右移1个字节(目的是获取0x0008 - 0x0014)最后与结果寄存器或运算。最终得到0x0007 - 0x0014地址段上的8个字节。

从以上分析过程来看,当CPU访问内存对齐的地址时,比如0x0000和0x0008这两个起始地址都是对齐至8的倍数。CPU可以通过一次read transaction读取出来。
但是当CPU访问内存没有对齐的地址时,比如0x0007这个起始地址就没有对齐至8的倍数。CPU就需要两次read transaction才能将数据读取出来。

  • 原子性

如CPU可以原子地操作一个对齐的word size memory。64位处理器中word size = 8字节。

  • 尽量分配在一个缓存行中

前边在介绍false sharding的时候我们提到目前主流处理器中的cache line大小为64字节,堆中对象的起始地址通过内存对齐至8的倍数,可以让对象尽可能的分配到一个缓存行中。一个内存起始地址未对齐的对象可能会跨缓存行存储,这样会导致CPU的执行效率慢2倍。
其中对象中字段内存对齐的其中一个重要原因也是让字段只出现在同一 CPU 的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值