问题 现代处理器上的内存对齐?


我经常看到如下代码,例如,在内存中表示一个大位图:

size_t width = 1280;
size_t height = 800;
size_t bytesPerPixel = 3;
size_t bytewidth = ((width * bytesPerPixel) + 3) & ~3; /* Aligned to 4 bytes */
uint8_t *pixelData = malloc(bytewidth * height);

(也就是说,一个位图被分配为一个连续的内存块,有一个 bytewidth 对齐到一定数量的字节,最常见的是4.)

然后通过以下方式给出图像上的一个点:

pixelData + (bytewidth * y) + (bytesPerPixel * x)

这引出了两个问题:

  1. 对齐像这样的缓冲区会对现代处理器产生性能影响吗?我应该担心对齐,还是编译器会处理这个问题?
  2. 如果它确实有影响,有人可以指向我找到各种处理器的理想字节对齐的资源吗?

谢谢。


2860
2017-12-06 17:06


起源



答案:


这取决于很多因素。如果您一次只访问一个字节的像素数据,则对齐在绝大多数情况下不会产生任何差异。对于读/写一个字节的数据,大多数处理器根本不关心该字节是否在4字节边界上。

但是,如果您以大于一个字节的单位(例如,以2字节或4字节为单位)访问数据,那么您肯定会看到对齐效果。对于某些处理器(例如许多RISC处理器),在某些级别访问未对齐数据是完全非法的:尝试从不是4字节对齐的地址读取4字节字将生成数据访问异常(或数据存储异常)例如,在PowerPC上。

在其他处理器(例如x86)上,允许访问未对齐的地址,但它通常会带来隐藏的性能损失。内存加载/存储通常在微代码中实现,微代码将检测未对齐的访问。通常,微代码将从内存中获取正确的4字节数量,但如果它没有对齐,则必须获取  来自存储器的4字节位置,并从两个位置的适当字节重建所需的4字节数量。获取两个内存位置显然比一个慢。

不过,这仅适用于简单的装载和存储。某些指令(例如MMX或SSE指令集中的指令)要求其存储器操作数正确对齐。如果您尝试使用这些特殊指令访问未对齐的内存,您将看到类似非法指令异常的内容。

总而言之,除非您编写超级性能关键代码(例如在汇编代码中),否则我不会太担心对齐。编译器会帮助你很多,例如通过填充结构使4字节数量在4字节边界上对齐,而在x86上,CPU在处理未对齐访问时也会帮助您。由于您正在处理的像素数据的数量为3个字节,因此您几乎总是会进行单字节访问。

如果您决定要在单个4字节访问中访问像素(而不是3个1字节访问),那么最好使用32位像素并使每个像素在4字节边界上对齐。将每行对齐到4字节边界但不是每个像素将具有很小的效果(如果有的话)。

基于你的代码,我猜测它与读取Windows位图文件格式有关 - 位图文件要求每个扫描行的长度是4个字节的倍数,因此使用该属性设置像素数据缓冲区具有以下属性:您可以一下子将整个位图读入缓冲区(当然,您仍然必须处理扫描线从下到上而不是从上到下存储的事实,并且像素数据是BGR而不是RGB)。尽管如此,这并不是一个很大的优势 - 一次扫描一个扫描线的位图并不是那么难。


7
2017-12-06 17:31





是的,对齐确实会对现代产生性能影响 - 比方说x86 - 处理器。通常,数据的加载和存储发生在自然对齐边界上;如果你在一个寄存器中得到一个32位的值,如果它已经在一个32位的边界上对齐,它将会是最快的。如果不是,那么x86将“为你照顾它”,因为CPU仍将负载,但是需要更多的周期来完成它,因为会有内部纠缠到“重新调整“访问权限”。

当然,在大多数情况下,这种开销是微不足道的。二进制数据的结构经常以未对齐的方式打包在一起,以便通过网络传输或在磁盘上进行持久化,并且打包存储的大小优势超过了偶尔对此数据进行操作的任何性能损失。

但特别是对于随机访问的统一数据的大缓冲区以及聚合中的性能确实很重要,如上面的像素缓冲区,保持数据结构对齐仍然是有益的。

请注意,对于上面给出的示例,只对齐像素数据的每个“行”。像素本身仍然是3个字节长,并且通常在“线”内不对齐,因此这里没有太大的好处。例如,有一些纹理格式,每个像素有3个字节的实际数据,实际上只是在每个像素上浪费一个额外的字节来保持数据对齐。

这里有一些更一般的信息: http://en.wikipedia.org/wiki/Data_structure_alignment

(架构之间的具体特性各不相同,无论是自然对齐,CPU是否自动处理未对齐的加载/存储,以及最终的处理成本如何。在CPU无法神奇地处理访问的情况下,通常是编译器/ C运行时将尽其所能为您完成此工作。)


4
2017-12-06 17:26





缓冲区对齐有影响。问题是:这是一个重大影响吗?答案可能很高 具体应用。在本身不支持未对齐访问的体系结构中 - 例如,68000和68010(68020添加了未对齐访问) - 这是真正的性能和/或维护问题,因为CPU会出错,或者可能陷入处理程序以执行未对齐访问。

可以估计各种处理器的理想对齐方式:4字节对齐适用于具有32位数据路径的架构。 64位的8字节对齐。但是,L1 缓存有效果。对于许多CPU来说,这是64字节,但它将来无疑会发生变化。

对齐太高(即,只需要两个字节的8字节)不会导致任何较窄系统的性能低效,即使在8位微控制器上也是如此。它只是浪费(可能)几个字节的存储空间。

你的例子相当特殊:3字节元素有50%的可能性单独未对齐(至32位),因此对齐缓冲区似乎毫无意义 - 至少出于性能原因。但是,在整体转移的情况下,它优化了第一次访问。请注意,未转移的第一个字节在传输到视频控制器时也可能会对性能产生影响。


1
2017-12-06 17:39





  • 对齐像这样的缓冲区会对现代处理器产生性能影响吗?

是。例如,如果使用SIMD指令(如MMX / SSE)优化memcpy,则对齐内存会使某些操作更快。在某些体系结构中,如果数据未对齐,则(处理器)指令会失败,因此某些内容可能在您的计算机上有效,但在另一台机器上则无效。

通过对齐数据,您还可以更好地利用CPU缓存。

  • 我应该担心对齐,还是编译器会处理这个问题?

当我使用动态内存并且编译器无法处理时,我应该担心对齐(请参阅对此注释的回复)。

对于代码中的其他内容,您可以使用-malign标志和对齐的属性。


1
2017-12-06 17:22



-malign与堆栈和代码对齐有关,这与此无关。内存分配单个 malloc 产生一个连续的块。如果行长 width*bytesPerPixel 不能被4整除(或本机字大小,或SIMD寄存器,或缓存行,具体取决于应用程序),然后对多行的访问将是未对齐的。上面的对齐有效地使每行略长于必要的长度,以便它们全部对齐。编译器无法执行此优化。但在这个例子中,额外的对齐是一个无操作,因为 1280*3 % 256 = 0。 - Jed
我知道 - 恶意。我一般都在谈论调整。 - arhuaco


答案:


这取决于很多因素。如果您一次只访问一个字节的像素数据,则对齐在绝大多数情况下不会产生任何差异。对于读/写一个字节的数据,大多数处理器根本不关心该字节是否在4字节边界上。

但是,如果您以大于一个字节的单位(例如,以2字节或4字节为单位)访问数据,那么您肯定会看到对齐效果。对于某些处理器(例如许多RISC处理器),在某些级别访问未对齐数据是完全非法的:尝试从不是4字节对齐的地址读取4字节字将生成数据访问异常(或数据存储异常)例如,在PowerPC上。

在其他处理器(例如x86)上,允许访问未对齐的地址,但它通常会带来隐藏的性能损失。内存加载/存储通常在微代码中实现,微代码将检测未对齐的访问。通常,微代码将从内存中获取正确的4字节数量,但如果它没有对齐,则必须获取  来自存储器的4字节位置,并从两个位置的适当字节重建所需的4字节数量。获取两个内存位置显然比一个慢。

不过,这仅适用于简单的装载和存储。某些指令(例如MMX或SSE指令集中的指令)要求其存储器操作数正确对齐。如果您尝试使用这些特殊指令访问未对齐的内存,您将看到类似非法指令异常的内容。

总而言之,除非您编写超级性能关键代码(例如在汇编代码中),否则我不会太担心对齐。编译器会帮助你很多,例如通过填充结构使4字节数量在4字节边界上对齐,而在x86上,CPU在处理未对齐访问时也会帮助您。由于您正在处理的像素数据的数量为3个字节,因此您几乎总是会进行单字节访问。

如果您决定要在单个4字节访问中访问像素(而不是3个1字节访问),那么最好使用32位像素并使每个像素在4字节边界上对齐。将每行对齐到4字节边界但不是每个像素将具有很小的效果(如果有的话)。

基于你的代码,我猜测它与读取Windows位图文件格式有关 - 位图文件要求每个扫描行的长度是4个字节的倍数,因此使用该属性设置像素数据缓冲区具有以下属性:您可以一下子将整个位图读入缓冲区(当然,您仍然必须处理扫描线从下到上而不是从上到下存储的事实,并且像素数据是BGR而不是RGB)。尽管如此,这并不是一个很大的优势 - 一次扫描一个扫描线的位图并不是那么难。


7
2017-12-06 17:31





是的,对齐确实会对现代产生性能影响 - 比方说x86 - 处理器。通常,数据的加载和存储发生在自然对齐边界上;如果你在一个寄存器中得到一个32位的值,如果它已经在一个32位的边界上对齐,它将会是最快的。如果不是,那么x86将“为你照顾它”,因为CPU仍将负载,但是需要更多的周期来完成它,因为会有内部纠缠到“重新调整“访问权限”。

当然,在大多数情况下,这种开销是微不足道的。二进制数据的结构经常以未对齐的方式打包在一起,以便通过网络传输或在磁盘上进行持久化,并且打包存储的大小优势超过了偶尔对此数据进行操作的任何性能损失。

但特别是对于随机访问的统一数据的大缓冲区以及聚合中的性能确实很重要,如上面的像素缓冲区,保持数据结构对齐仍然是有益的。

请注意,对于上面给出的示例,只对齐像素数据的每个“行”。像素本身仍然是3个字节长,并且通常在“线”内不对齐,因此这里没有太大的好处。例如,有一些纹理格式,每个像素有3个字节的实际数据,实际上只是在每个像素上浪费一个额外的字节来保持数据对齐。

这里有一些更一般的信息: http://en.wikipedia.org/wiki/Data_structure_alignment

(架构之间的具体特性各不相同,无论是自然对齐,CPU是否自动处理未对齐的加载/存储,以及最终的处理成本如何。在CPU无法神奇地处理访问的情况下,通常是编译器/ C运行时将尽其所能为您完成此工作。)


4
2017-12-06 17:26





缓冲区对齐有影响。问题是:这是一个重大影响吗?答案可能很高 具体应用。在本身不支持未对齐访问的体系结构中 - 例如,68000和68010(68020添加了未对齐访问) - 这是真正的性能和/或维护问题,因为CPU会出错,或者可能陷入处理程序以执行未对齐访问。

可以估计各种处理器的理想对齐方式:4字节对齐适用于具有32位数据路径的架构。 64位的8字节对齐。但是,L1 缓存有效果。对于许多CPU来说,这是64字节,但它将来无疑会发生变化。

对齐太高(即,只需要两个字节的8字节)不会导致任何较窄系统的性能低效,即使在8位微控制器上也是如此。它只是浪费(可能)几个字节的存储空间。

你的例子相当特殊:3字节元素有50%的可能性单独未对齐(至32位),因此对齐缓冲区似乎毫无意义 - 至少出于性能原因。但是,在整体转移的情况下,它优化了第一次访问。请注意,未转移的第一个字节在传输到视频控制器时也可能会对性能产生影响。


1
2017-12-06 17:39





  • 对齐像这样的缓冲区会对现代处理器产生性能影响吗?

是。例如,如果使用SIMD指令(如MMX / SSE)优化memcpy,则对齐内存会使某些操作更快。在某些体系结构中,如果数据未对齐,则(处理器)指令会失败,因此某些内容可能在您的计算机上有效,但在另一台机器上则无效。

通过对齐数据,您还可以更好地利用CPU缓存。

  • 我应该担心对齐,还是编译器会处理这个问题?

当我使用动态内存并且编译器无法处理时,我应该担心对齐(请参阅对此注释的回复)。

对于代码中的其他内容,您可以使用-malign标志和对齐的属性。


1
2017-12-06 17:22



-malign与堆栈和代码对齐有关,这与此无关。内存分配单个 malloc 产生一个连续的块。如果行长 width*bytesPerPixel 不能被4整除(或本机字大小,或SIMD寄存器,或缓存行,具体取决于应用程序),然后对多行的访问将是未对齐的。上面的对齐有效地使每行略长于必要的长度,以便它们全部对齐。编译器无法执行此优化。但在这个例子中,额外的对齐是一个无操作,因为 1280*3 % 256 = 0。 - Jed
我知道 - 恶意。我一般都在谈论调整。 - arhuaco