内存对齐有多重要?仍然重要吗?


15

从现在开始,我已经搜索并阅读了很多有关内存对齐方式,其工作方式和使用方法的内容。我现在找到的最相关的文章是这篇

但是即使如此,我仍然对此有一些疑问:

  1. 在嵌入式系统之外,我们经常在计算机中拥有大量内存,这使内存管理的批评家减少了很多。我完全致力于优化,但是现在,如果我们将相同的程序与或进行比较,是否真的可以有所作为?没有它的内存重新排列和对齐?
  2. 内存对齐还有其他优势吗?我在某处读到CPU可以更好地/更快地使用对齐的内存,因为这样可以减少处理的指令(如果你们中的某个人有一篇文章/基准的链接?),在那种情况下,区别真的很重要吗?有没有比这两个更多的优势?
  3. 在第5章的文章链接中,作者说:

    当心:在C ++中,看起来像结构的类可能会违反此规则!(它们是否取决于基类和虚拟成员函数的实现方式,并随编译器的不同而不同。)

  4. 本文主要讨论结构,但是局部变量声明是否也受此需求影响?

    您是否知道内存对齐在C ++中如何工作,因为它似乎有些差异?

前一个问题包含“对齐”一词,但未提供上述问题的任何答案。


C ++编译器更愿意为您执行此操作(在需要或有益的地方插入填充)。在您提到的链接中,在第12节“工具”下查找可以使用的东西。
rwong

Answers:


11

是的,数据的对齐和排列都可以对性能产生很大的影响,不仅是百分之几,而且是百分之几到几百。

进行此循环,如果您运行了足够的循环,则两条指令很重要。

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

使用和不使用缓存,以及在分支预测中使用和不使用缓存折腾,您都可以将这两个指令的性能相差很大(计时器滴答):

min      max      difference
00016DDE 003E025D 003C947F

您可以轻松进行性能测试。在被测代码周围添加或删除nops并准确计时,将被测指令沿足够宽的地址范围移动以接触高速缓存行的边缘,等等。

数据访问也是如此。一些体系结构通过给您提供数据错误来抱怨未对齐的访问(例如,在地址0x1001执行32位读取)。其中一些您可以禁用故障并降低性能。其他允许未对齐访问的应用程序只会降低性能。

有时是“指令”,但大多数时候是时钟/总线周期。

查看gcc中针对各种目标的memcpy实现。假设您要复制一个0x43字节的结构,您可能会发现一个实现,该实现将一个字节复制为0x42,然后以较大的有效块复制0x40字节,然后最后一个0x2可以作为两个单独的字节或16位传输。如果源地址和目标地址位于同一对齐位置(例如0x1003和0x2003),则对齐和目标起作用,然后您可以执行一个字节,然后大块执行0x40,然后执行0x2,但是如果其中一个为0x1002,另一个为0x1003,则它将得到真正的丑陋和真正的缓慢。

在大多数情况下,这是公共汽车周期。或更糟糕的是转账次数。以具有64位宽数据总线的处理器(例如ARM)为例,并在地址0x1004处进行四字传输(读或写,LDM或STM),这是一个字对齐的地址,并且完全合法,但是如果总线为64在这种情况下,单个指令可能会转换为三个传输,即0x1004处的32位,0x1008处的64位和0x100A处的32位。但是,如果您在地址0x1008处执行相同的指令,则可以在地址0x1008处执行一个四字传输。每次传输都有关联的建立时间。因此,使用缓存时,0x1004到0x1008的地址差本身可以快几倍,甚至是/ esp,而所有都是缓存命中。

说到即使您在地址0x1000与0x0FFC上进行了两个字读取,具有高速缓存未命中的0x0FFC也会导致两次高速缓存行读取,其中0x1000是一个高速缓存行,您仍然要随机读取一条高速缓存行访问(读取的数据多于使用的数据),但然后翻倍。您的结构如何对齐或数据总体上以及访问该数据的频率等可能会导致高速缓存崩溃。

您可以最终对数据进行条带化,以便在处理数据时可以创建驱逐,您可能会倒霉,最终仅使用缓存的一小部分,并且当您跳过它时,下一个数据块与先前的块冲突。通过混合数据或在源代码中重新安排函数等,您可以创建或删除冲突,因为并非所有缓存都创建时,编译器无法帮助您。甚至检测性能下降或改善都在您身上。

我们为提高性能而添加的所有内容,更广泛的数据总线,管道,高速缓存,分支预测,多个执行单元/路径等。通常会有所帮助,但它们都有薄弱环节,可以有意或无意地加以利用。编译器或库对此无能为力,如果您对性能感兴趣,则需要调整,最大的调整因素之一是代码和数据的对齐,而不仅仅是在32、64、128、256上对齐位边界,以及彼此之间相对相关的位置,您希望频繁使用的循环或重复使用的数据不要以相同的缓存方式降落,它们各自需要自己的缓存。编译器可以帮助例如对超标量体系结构的指令进行排序,重新排列彼此无关紧要的指令,

最大的疏忽是假设处理器是瓶颈。十年或更长时间以来,一直不是真的,向处理器供电是问题所在,这就是对齐性能下降,高速缓存抖动等问题发挥作用的地方。即使在源代码级别上只需做一点工作,在结构中重新排列数据,对变量/结构声明的排序,在源代码中对函数的排序以及一些用于对齐数据的额外代码,都可以将性能提高数倍。更多。


+1(仅用于最后一段)。对于当今尝试编写快速代码而不是指令计数的任何人来说,内存带宽是最关键的问题。这意味着,在许多情况下可以通过修改对齐来优化事物以减少高速缓存未命中,这一点非常重要。
Jules

如果您的代码和数据被缓存,并且您对该数据执行了足够的循环/循环,则指令计数以及指令位于访存行中的位置,分支相对于它们所依赖的内容落在管道中的位置都非常重要。但是在基于dram和/或flash的系统中,您首先必须担心会给处理器供电。
old_timer

15

是的,内存对齐仍然很重要。

某些处理器实际上无法对未对齐的地址执行读取。如果您在这样的硬件上运行,并且存储未对齐的整数,则可能必须先阅读两条指令,然后再阅读一些指令,以将各个字节放入正确的位置,以便实际使用它。因此,对齐的数据对性能至关重要。

好消息是您实际上基本上不必关心。几乎所有语言的几乎所有编译器都将生成符合目标系统对齐要求的机器代码。如果您要直接控制数据的内存中表示,则只需要考虑一下就可以了,而这在任何时候都没有必要像现在这样频繁地进行。了解这是一件很有趣的事情,并且绝对至关重要,要知道您是否要了解正在创建的各种结构的内存使用情况,以及如何重新组织事情以提高效率(避免填充)。但是,除非您需要这种控制(对于大多数系统来说就是不需要),否则您可以不知不觉地乐于经历整个职业。


1
特别是,ARM不支持不对齐访问。这就是移动设备几乎所有使用的CPU。
Jan Hudec

还要注意,Linux会以一定的运行时间成本来模拟非对齐访问,但是Windows(CE和Phone)却不会,并且尝试非对齐访问只会使应用程序崩溃。
Jan Hudec

2
尽管大多数情况都是如此,但请注意,某些平台(包括x86)根据要使用的指令具有不同的对齐要求,这对于编译器本身来说并不容易,因此有时需要填充以确保某些操作(例如,SSE指令,其中许多需要16字节对齐)可以用于某些操作。另外,添加额外的填充,使经常一起使用的两个项目出现在同一高速缓存行(也为16字节)上,在某些情况下会对性能产生巨大影响,并且也不是自动化的。
Jules

3

是的,它仍然很重要,在某些性能至关重要的算法中,您不能依赖编译器。

我将仅列出几个示例:

  1. 这个答案

通常,微码将从内存中获取适当的4字节数量,但是如果未对齐,则必须从内存中获取两个4字节位置,并从两个位置的适当字节中重建所需的4字节数量

  1. SSE指令集需要特殊的对齐方式。如果不满足,则必须使用特殊功能将数据加载并存储到未对齐的内存中。这意味着另外两条指令。

如果您不是在使用对性能至关重要的算法,则只需忽略内存对齐。正常编程并不需要。


1

我们倾向于避免重要的情况。如果重要,那就重要。例如,在处理二进制数据时,经常会发生未对齐的数据,如今似乎已经避免了这种情况(人们经常使用XML或JSON)。

如果您设法以某种方式创建了一个未对齐的整数数组,那么在典型的英特尔处理器上,处理该数组的代码将比对齐数据慢一些。如果您告诉编译器数据未对齐,则在ARM处理器上运行速度会稍慢。如果您使用未对齐的数据而不告知编译器,则它可能运行得非常糟糕,运行速度慢得多或给出错误的结果,具体取决于处理器型号和操作系统。

解释对C ++的引用:在C中,结构中的所有字段都必须以升序存储。因此,如果您具有char / double / char字段,并且想要使所有内容对齐,则将有一个字节的char,未使用的七个字节,八个字节的double,一个字节的char,七个未使用的字节。在C ++结构中,兼容性相同。但是对于结构,编译器可能会对字段进行重新排序,因此您可能拥有一个字节的char,另一个字节的char,未使用的六个字节,双精度的8个字节。使用16个字节而不是24个字节。在C结构中,开发人员通常会避免这种情况,并首先将字段按不同的顺序排列。


1
未对齐的数据发生在内存中。没有正确打包数据结构的程序可能会遭受严重的性能损失,即使是看似无关紧要的值排序也是如此。例如,在lthreaded代码中,当两个线程同时访问它们时,单个高速缓存行中的两个值将导致大量的管道停顿(当然,忽略线程安全性问题)。
greyfade

C ++编译器只能在某些条件下对字段进行重新排序,如果您不了解这些规则,则可能无法满足这些条件。最重要的是,我不知道实际使用这种自由的任何C ++编译器。
Sjoerd

1
我从未见过C编译器对字段进行重新排序。例如,我已经看到许多插入填充和字符/整数之间的对齐..
PaulHK,2016年


1

内存对齐有多重要?仍然重要吗?

是。不,这要看情况。

在嵌入式系统之外,我们经常在计算机中拥有大量内存,这使内存管理的批评家减少了很多。我完全致力于优化,但是现在,如果我们将相同的程序与或进行比较,是否真的可以有所作为?没有它的内存重新排列和对齐?

如果正确对齐,您的应用程序将具有较小的内存占用空间并可以更快地运行。在典型的桌面应用程序中,在极少数/非典型情况下都无所谓(例如,您的应用程序始终以相同的性能瓶颈结尾并需要进行优化)。也就是说,如果正确对齐,该应用将更小,更快,但是在大多数实际情况下,它不会以一种或另一种方式影响用户。

内存对齐还有其他优势吗?我在某处读到CPU可以更好地/更快地使用对齐的内存,因为这样可以减少处理的指令(如果你们中的某个人有一篇文章/基准的链接?),在那种情况下,区别真的很重要吗?有没有比这两个更多的优势?

有可能。编写代码时(可能)要牢记这一点,但是在大多数情况下,这根本不重要(也就是说,我仍然按内存占用量和访问频率排列成员变量-应该可以简化缓存-但我这样做是为了易于使用/读取和重构代码,而不用于缓存目的)。

您是否知道内存对齐在C ++中如何工作,因为它似乎有些差异?

我在alignof东西问世时就读到了它(C ++ 11?),因为我(现在我主要从事台式机应用程序和后端服务器开发),所以我对此并不打扰。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.