诚然,我不太理解。假设您有一个内存单元,其内存单元字长为1字节。为什么不能在非对齐地址(即不能被4整除的地址)上通过单次内存访问访问4个字节长的变量,就像对齐地址一样?
诚然,我不太理解。假设您有一个内存单元,其内存单元字长为1字节。为什么不能在非对齐地址(即不能被4整除的地址)上通过单次内存访问访问4个字节长的变量,就像对齐地址一样?
struct mystruct {
char c; // one byte
int i; // four bytes
short s; // two bytes
}
读取第一个字节将是相同的。
当你要求处理器从0x0005给你16位时,它将从0x0004读取一个字并将其左移1个字节放入16位寄存器中;有一些额外的工作,但大多数处理器可以在一个周期内完成。
当你要求从0x0001获取32位时,会得到2倍的放大。处理器将从0x0000读取结果到寄存器中并左移1个字节,然后再次从0x0004读取到临时寄存器中,并右移3个字节,然后与结果寄存器进行OR
操作。
CPU可以原子地操作对齐的内存字,这意味着没有其他指令可以中断该操作。这对于许多无锁数据结构和其他并发编程范例的正确运行至关重要。
处理器的内存系统比此处描述的更为复杂和涉及广泛;论述x86处理器如何实际寻址内存可以提供帮助(许多处理器的工作方式类似)。
遵循内存对齐还有许多其他好处,您可以在这篇IBM文章中阅读到。
计算机的主要用途是转换数据。现代内存架构和技术经过数十年的优化,以高度可靠的方式实现更多数据在更多和更快的执行单元之间的输入、输出和传输。mystruct
的对齐方式不正确。C 结构体总是按照其最大成员的对齐方式进行对齐,因此在 s
后应该有两个额外的填充字节。 - Martin你可以使用一些处理器(像 Nehalem 处理器),但以前所有的内存访问都是在 64 位(或 32 位)线上对齐的,因为总线宽度是 64 位,所以必须一次获取 64 位,将其按照对齐的 64 位“块”显著更容易获取。
所以,如果你想要一个单字节,你需要获取 64 位块,然后屏蔽掉你不需要的位。如果你需要的字节位于这 64 位块的中间,那么你就得屏蔽掉不需要的位,然后将数据移动到正确的位置。更糟糕的是,如果你需要一个由两个字节变量组成的变量,但它被分割成了两个块,那么就需要双倍的内存访问。
因此,由于所有人都认为内存是廉价的,他们只需让编译器将数据对齐到处理器的块大小,使您的代码以牺牲内存为代价运行更快,更有效率。
根本原因是内存总线长度比内存大小小得多。
因此,CPU从芯片上的L1缓存中读取数据,这些天通常为32KB。但连接L1缓存和CPU的内存总线的宽度将远远小于缓存行大小。这将在128位左右。
因此:
262,144 bits - size of memory
128 bits - size of bus
@joshperry已经对这个问题给出了一个很好的答案。除了他的回答之外,我还有一些数字可以图形化地展示所描述的影响,特别是2倍放大效应。这里是一个链接到Google电子表格,展示不同单词对齐方式的效果。 此外,这里还有一个Github gist的链接,其中包含测试代码。 测试代码改编自Jonathan Rentzsch写的文章,@joshperry也引用了这篇文章。这些测试在一台配备四核2.8 GHz Intel Core i7 64位处理器和16GB RAM的Macbook Pro上运行。
在 PowerPC 上,您可以从奇地址加载整数而没有任何问题。
Sparc 和 I86 以及 (我认为) Itatnium 在尝试此操作时会引发硬件异常。
在大多数现代处理器上,一次32位加载与四次8位加载的区别不会太大。数据是否已经在缓存中将产生更大的影响。