深究“字节对齐”的原因
到底为什么要进行字节对齐?
1.hpsmouse
CPU 的访问粒度不仅仅是大小限制,地址上也有限制。也就是说,CPU 只能访问对齐地址上的固定长度的数据。
以四字节对齐为例,就是只能访问 0x0 - 0x3,0x4 - 0x7, 0x8 - 0xc 这样的(闭)区间,不能跨区间访问。
如果真正需要访问的数据并没有占据那个区间的全部字节范围,还有另外的信号线来指出具体操作哪几个字节,类似于掩码的作用。好像也有些架构干脆就不允许这种部分访问,强制要求按粒度访问。
如果一个数据跨越了两个这样的区间,那么就只能将这个数据的操作拆分成两部分,分别执行,效率当然就低了。
解决这个问题的一个办法就是强制数据对齐,现在假设一个 16 字节对齐的系统(稍微新一点的 x86 架构应该都是 16 字节对齐):
显然,一个字节无论如何不会跨越两个对齐区间;
当一个 2 字节的数据放在奇数地址上,就有可能跨越两个区间,但放在偶数地址上(即 2 字节对齐)就肯定不会;
同样,只要 4 字节数据放在 4 字节地址上,8 字节数据放在 8 字节地址上,一定不会跨越两个区间。
这就是比较常见的字节对齐策略,将长度小于一个对齐单位的数据对齐到不小于它的长度的首个二的幂次。(晕,这句话好绕……)
2.v2sun
1)CPU在取数据时使用缓存,缓存每次缓冲固定大小的数据(以4Byte为例),而且是在特定的内存地址上预取数据(在这里是4的倍数);故如果把一个4字节的变量放在非4的倍数的内存地址上,则需要两次预读取才可完成变量访问,便降低了访问速率。
这也是为什么结构体变量定义建议把大变量放在前面的原因吧。
2)使用#pragma pack(n)不一定会降低cpu效率呀。
内存对齐也只是一个在一般情况下提高cpu效率的算法而已,如果你只是处理char,而仍使用4字节的内存对齐,那效率也不会提高;如果大量处理8字节变量而使用4字节内存对齐,那也不一定能保证内存访问的命中率。
3)cpu从内存读取数据时总是对齐的;而数据在内存中却不一定是按内存对齐格式存放的。
4)你可以把内存当成一个中介,你把一些数据给内存,要么紧挨着放(非内存对齐),要么一段一段地放(内存对齐)。
放上之后,cpu对这些数据进行访问处理,它每次都要拿固定的一段(你不能改变),如果说你的某个数据跨段放了,它得拿两次才行,这样效率就降低了;如果你是一段一段放的,那就能保证它拿最少的次数就可以把想要数据拿到。
5)
Optimizing Memor y Access Memory access is one of the slowest functions the processor performs. When writing assembly language programs that require high performance, it is best to avoid memory access as much as possible. Whenever possible, it is best to keep variables in registers on the processor. Register access is highly optimized for the processor, and is the quickest way to handle data. When it is not possible to keep all of the application data in registers, you should try to optimize the memory access for the application. For processors that use data caching, accessing memory in a sequen- tial order in memory helps increase cache hits, as blocks of memory will be read into cache at one time. One other item to think about when using memory is how the processor handles memory reads and writes. Most processors (including those in the IA-32 family) are optimized to read and write memory locations in specific cache blocks, beginning at the start of the data section. On a Pentium 4 processor, the size of the cache block is 64 bits. If you define a data element that crosses a 64-bit block boundary, it will require two cache operations to retrieve or store the data element in memory. To solve this problem, Intel suggests following these rules when defining data: ❑ Align 16-bit data on a 16-byte boundary. ❑ Align 32-bit data so that its base address is a multiple of four. ❑ Align 64-bit data so that its base address is a multiple of eight. ❑ Avoid many small data transfers. Instead, use a single large data transfer. ❑ Avoid using larger data sizes (such as 80- and 128-bit floating-point values) in the stack. Aligning data within the data section can be tricky. The order in which data elements are defined can be crucial to the performance of your application. If you have a lot of similarly sized data elements, such as integer and floating-point values, place them together at the beginning of the data section. This ensures that they will maintain the proper alignment. If you have a lot of odd-sized data elements, such as strings and buffers, place those at the end of the data section so they won’t throw off the alignment of the other data elements.这是《Professional Assembly Language》里的一段。
大意是:
内存访问是处理器执行的功能里很慢的一个,所以要写出高性能的程序,就要尽量减少内存访问。
对于使用数据缓存的处理器,按顺序访问内存可以提高缓存命中率,因为每次有一段的内存被读取到缓存里。
大多处理器(包括IA-32系列)被优化而对特定的缓存块进行内存读写。在奔四处理器上,缓存块大小是64位。如果你定义了一个跨过64位界限的数据,它会执行两次缓存操作来提取或存储此数据。
为解决这个问题,Intel建议在定义数据时遵守那几条规则,即把n字节的数据定义在n的倍数的内存地址上进行对齐。
所以,对于大量大小相似的数据元素(如整型、浮点型)最好把它们一起放到开头;而有些奇数大小的数据(如字符串、缓冲区),最好把它们放到后面。
3.lxyppc
1. CPU不会因为你的pack设置而改变自己的访问方式,你代码里对齐值是多少,CPU并不不知道
2. CPU在访问对齐数据和没有对齐的数据的时候会使用不同的方法,不同的方法效率。cpu可以访问放在奇数地址的4字节数据,只是效率上与放在4的倍数地址上的4字节数据不同。(效率是指时间和空间上的)
3. 对齐的数据是指数据所在地址能被数据大小整除。如:1个字节的数据在任何位置都是对齐的。2个字节的数据在偶数地址上是对齐的。4个字节的数据在4的倍数地址上是对齐的。
以上是针对cpu而言的,和编译器无关。
而编译器的pack开关上面已经说过了。你改变的是你代码中数据的对齐方式,这种方式上的改变会造成有的数据没有对齐。
比如4字节的数据放在了奇数地址。对cpu而言,奇数地址上的4字节数据是没有对齐的,cpu在访问的时候效率上有不同。
pack开关影响的是数据在内存中的排列方式。cpu可以访问任何对齐形式的数据,只是效率不同。