内存对齐后,可以达到更高的性能。但背后的原理是什么呢?本文从内存的物理组成方式,和读取原理上进行说明。
内存的物理组织方式
- 电脑主板上的内存插槽就是Channel,一个Channel可以插入一个内存条。
- 内存条通常是DIMM(Dual In-Line Memory Modules)模式,即正反两面的引脚是独立的。每一面都是一个Rank。
- 一个Rank由多个Chip组成,如图中4个黑色颗粒,也可能是8个。
- 每个Chip由多个Bank组成,图中是8个。
内存控制器通过设置rank select的bit位,来控制读哪个rank。
读取数据的过程
每个Bank是DRAM Chip中独立的矩阵。
下面先来解释memory controllers如何从rank中取数据。因为每个rank下面会有很多chip,而每个chip又包括bank0、bank1、bank2等,在memory controllers看来每次发数据,都会同时发送给所有chip下的某个bank,并声明row和col。
每个chip的bank0 的同一地点(row=i col=j)都会被读出8bit,那么8个chip就会同时读出64bit,然后由memory controllers传送给cpu,也就是8byte。每个bank只保存了1个字节。在物理位置上,读出来的64 bits数据并不连续。
在memory controllers看来,每个bank存在于每个chip中,如上图所示,可以把每个chip里面的小bank连成一行,看作成一个大的bank。然后从大的bank中读取数据。
每个bank有一个row bufffer,作为一个bank page,所有bank共享地址、数据总线,但是每个channel有他们自己的地址、数据总线。正因为有buffer,所以每次bank都会预读64bit的数据。
那么为什么要这样设计呢?原因是提高了电路的工作效率。不同chip下的bank可以并行工作。如果你想读取地址0x0000-0x0007,每个bank工作一次,拼起来就是你要的数据,IO效率会比较高。但要存在一个bank里,那这个bank只能自己干活。只能串行进行读取,需要读8次,这样速度会慢很多。
结论
所以,内存对齐最最底层的原因是内存的IO是以8个字节64bit为单位进行的。 对于64位数据宽度的内存,假如cpu也是64位的cpu(现在的计算机基本都是这样的),每次内存IO获取数据都是从同行同列的8个bank中各自读取一个字节拼起来的。从内存的0地址开始,0-7字节的数据可以一次IO读取出来,8-15字节的数据也可以一次读取出来。
换个例子,假如你指定要获取的是0x0001-0x0008,也是8字节,但是不是0开头的,内存需要怎么工作呢?没有好办法,内存只好先工作一次把0x0000-0x0007取出来,然后再把0x0008-0x0015取出来,把两次的结果都返回给你。CPU和内存IO的硬件限制导致没办法一次跨在两个数据宽度中间进行IO。这样你的应用程序就会变慢,算是计算机因为你不懂内存对齐而给你的一点点惩罚。
延伸
另外,在cpu cache中有cache line的概念,为每次读内存的最小单位,为64 bytes,需要也8次这样的读操作。写入到buffer中,这就是局部性原理。如果我们程序猿不尊重这个规则,也就迫使bank的buffer每次取值都必须清空当前的缓冲区,重新读数据,降低数据的访问速度。
参考链接
http://thebeardsage.com/dram-nomenclature-explained/
https://mp.weixin.qq.com/s/F0NTfz-3x3UxQeF-GSavRg
https://lzz5235.github.io/2015/04/21/memory.html
https://www.youtube.com/watch?v=rTxsO9DVjNk