为什么要求内存对齐
说到内存对齐,很多人都知道是怎么回事。但是内存对齐该娘不是本文的重点,本文的重点是内存对齐有什么好处。
CPU访问某个数据时,要求其存储地址必须是相应数据类型的自然边界。对于存储地址不在其相应类型自然边界的数据,不支持非对齐数据访问的CPU,会导致CPU异常;即使是支持非对齐数据访问的CPU,也会严重影响程序效率。
假设非对齐访问出现在位于操作系统之上的进程,且CPU不支持非对齐数据访问,那么对于出现CPU异常的情况,可能操作系统会对其进行处理,(1)将所需要的数据装载,并返回,或者说(2)直接让进程死掉。情形(2)不需要多做解释;对情况(1)来说,非对齐访问每次都要进入异常处理程序,相比于一条指令直接拿到数据,效率极其低下。
假设非对齐访问出现在直接位于硬件之上的进程,且CPU不支持非对齐数据访问,那么对于出现CPU异常的情况来说,基本上的直观反应是进程退出并出现堆栈信息。
接下来进一步对非对齐数据装载进行分析。
如下图,|-|表示自然边界,假设存在两个连续的位于自然边界上的64位数据,当CPU访问它们中的任何一个的时候,可以一条LD指令就能完成加载
|-|BBBBBBBB|-|BBBBBBBB
现在假设有一个8字节数据如下,|表示数据开始位置,|-|表示自然边界
|-|BBBBB|BBB|-|BBBBB|BBB
其前三字节为前一个对齐的八字节数据的后三字节,其后五字节为后一个对齐的八字节数据的前五字节
对于不支持非对齐装载指令的CPU来说,要装载这样的一个数据,需要先装载前一个八字节数据,再装载后一个八字节数据,然后将前一个八字节数据的后三字节与后一个八字节数据的前五字节数据合并才能得到结果,与对齐数据的访问相比,多了一个装载指令以及相关合并指令的开销,一般来说,在忽视缓存未命中的情况下,装载指令的执行与得到结果之间是存在额外开销的,因此这个差别是很大的,何况上边说的,假设是在操作系统对CPU异常进行处理时为其加载数据,那么异常处理程序的开销可能更大;对非对齐数据的写入时也需要额外的加载,合并操作。
即使对于支持非对齐数据加载的CPU,依然会极大的影响效率,差别只是它省略掉了CPU异常处理过程。
再进一层,假设之前描述的非对齐数据刚好横跨两个cache line,而且这两个cache line至少有一个不在cache中(虽然对齐数据也会存在未命中,但是与非对齐相比,它不会横跨两个cache line),那么这个访问效率绝对不是多几十条指令的问题了。
因此,内存不对齐的坏处不是浪费内存,因为即使我写一个随便在不同位置放置不同大小的数据结构时,只要告诉编译器说必须按照一字节对齐,编译器编译时肯定按照我的意愿不浪费一个字节的内存。编译器默认按照自然边界对齐,是因为它要求效率,保证程序的正常运行(因为非对齐访问可能导致进程退出)。我们对结构体的组织的调整是为了节约内存,而调整的规则就是按照内存对齐来安插数据。