诚然我不明白。假设您有一个存储器,其存储器字的长度为1个字节。为什么不能在未对齐地址(即不能被4整除)的单次存储器访问中访问4字节长的变量,就像对齐地址的情况一样?

评论

在进行了一些额外的谷歌搜索之后,我发现了这个很棒的链接,这很好地解释了问题。

请查看这篇小文章,了解那些开始学习此文章的人:blog.virtualmethodstudio.com/2017/03/memory-alignment-run-fools

@ark链接已损坏

@JohnJiang我想我在这里找到了新链接:developer.ibm.com/technologies/systems/articles/pa-dalign

#1 楼

这是许多基础处理器的局限性。通常可以通过执行4次低效率的单字节读取而不是一次有效的字读取来解决此问题,但是许多语言说明者认为,将它们取缔并强制所有内容对齐将更容易。

有OP在此链接中发现的更多信息。

#2 楼

现代处理器上的内存子系统仅限于按其字长的粒度和对齐方式来访问内存。这是有多种原因的。

速度

现代处理器具有多个级别的高速缓存,必须通过这些高速缓存来提取数据。支持单字节读取将使内存子系统的吞吐量与执行单元的吞吐量紧密绑定(也称为cpu绑定);由于硬盘驱动器中的许多相同原因,这一切都让人想起DMA超过PIO模式的原因。
CPU始终以其字长(32位处理器上为4字节)读取数据,因此当您执行不对齐的地址访问时(在支持该地址的处理器上),该处理器将读取多个字。 CPU将读取您请求的地址跨越的每个内存字。这导致访问请求的数据所需的内存事务数量最多增加2倍。因此,读取两个字节比读取四个字节要容易得多。例如,假设您的内存中的结构看起来像这样:

struct mystruct {
    char c;  // one byte
    int i;   // four bytes
    short s; // two bytes
}


在32位处理器上,它很可能按照如下所示对齐:



处理器可以在一个事务中读取这些成员中的每个成员。

假设您有该结构的打包版本,也许是从网络上获取的它被包装以提高传输效率;它可能看起来像这样:



读取第一个字节将是相同的。

当您要求处理器给出您从0x0005开始的16位将必须从0x0004读取一个字并将其左移1个字节以将其放入16位寄存器中;一些额外的工作,但大多数可以一次完成。

当您从0x0001请求32位时,您将获得2倍的放大倍数。处理器将从0x0000读入结果寄存器并向左移1个字节,然后再次从0x0004读入一个临时寄存器,向右移3个字节,然后用结果寄存器OR对其进行处理。

范围

对于任何给定的地址空间,如果体系结构可以假定2个LSB始终为0(例如32位计算机),则它可以访问4倍以上的内存(2个保存的位可以表示4个不同的状态),或具有2位的相同数量的内存(用于标志)。如果从地址中减去2个LSB,则会使您对齐4字节。也称为4字节跨度。每次地址增加时,它实际上是在增加位2,而不是位0,即,最后2位将始终为00

这甚至可能影响系统的物理设计。如果地址总线需要少2位,则CPU上的引脚可以少2个,电路板上的迹线也可以少2个。

原子性

CPU可以在原子对齐的内存字,表示没有其他指令可以中断该操作。这对于正确操作许多无锁数据结构和其他并发范例至关重要。

结论

处理器的存储系统要复杂得多,并且涉及比这里描述的关于x86处理器实际如何处理内存的讨论会有所帮助(许多处理器的工作方式类似)。

坚持内存对齐还有很多好处,您可以在此IBM文章中阅读。

计算机的主要用途是转换数据。现代内存体系结构和技术经过数十年的优化,以一种高度可靠的方式促进在更多和更快的执行单元之间取入,取入和取回更多数据。

奖金:缓存

我之前提到的另一项性能提升是在(例如在某些CPU上)64B的高速缓存行上进行的对齐。

有关利用缓存可获取多少性能的更多信息,看一看处理器缓存效果库;从有关高速缓存行大小的问题开始,


了解高速缓存行对于某些类型的程序优化可能很重要。例如,数据的对齐可以确定操作是触摸一条还是两条高速缓存行。正如我们在上面的示例中看到的,这很容易意味着在未对齐的情况下,操作将慢两倍。


评论


如果我理解正确,为什么计算机不能一步一步读取未对齐字的原因是因为地址位使用的是30位而不是32位?

– GetFree
2014年6月16日22:50

@chux是的,绝对不成立。 8088是一项有关速度和成本之间折衷的有趣研究,它基本上是一个16位的8086(具有完整的16位外部总线),但是只有一半的总线可以节省生产成本。因此,访问8088所需的时钟周期是8086的两倍,因为它必须进行两次读取才能获得完整的16位字。有趣的是,8086可以在一个周期内完成一个字对齐的16位读取操作,而未对齐的读取则需要2个操作。8088具有半字总线的事实掩盖了这种减速。

– joshperry
14年6月22日在17:40

@joshperry:稍作纠正:8086可以在四个周期内进行字对齐的16位读取,而未对齐的读取则需要八次。由于内存接口的速度较慢,基于8088的计算机上的执行时间通常受指令提取支配。像“ MOV AX,BX”这样的指令名义上要比“ XCHG AX,BX”快一个周期,但是除非在该指令的前面或之后执行每个代码字节超过四个周期的指令,否则它将花费更长的四个周期。执行。在8086上,代码获取有时可以跟上执行的速度,但在8088上,除非使用...

–超级猫
2015年3月1日,3:19

真的,@ martin。我省略了这些填充字节以使讨论集中在结构内,但最好将它们包括在内。

– joshperry
15年12月16日在20:04

您是指高速缓存行为64B(字节)吗?

–狮子座
17年2月14日在16:28

#3 楼

您可以使用某些处理器(nehalem可以做到这一点),但是以前所有内存访问都在64位(或32位)线上对齐,因为总线为64位宽,因此您必须一次获取64位,并且以64位对齐的“块”来获取这些数据要容易得多。

因此,如果要获取单个字节,则需要获取64位块,然后屏蔽掉这些位你不想要如果字节位于正确的末端,则容易又快速,但是如果它位于该64位块的中间,则必须屏蔽掉不需要的位,然后将数据移至正确的位置。更糟糕的是,如果您想要一个2字节的变量,但是将其分成2个块,则需要两倍的所需内存访问。

因此,由于每个人都认为内存便宜,所以他们只是使编译器对齐处理器块大小上的数据,因此您的代码可以更快,更高效地运行,但会浪费内存。

#4 楼

从根本上讲,原因是因为内存总线具有一些特定的长度,该长度远小于内存大小。

因此,CPU读取片上L1缓存,该缓存通常为32KB这些日子。但是,将L1高速缓存连接到CPU的内存总线的高速缓存行大小的宽度要小得多。这将是128位的数量级。

因此:

262,144 bits - size of memory
    128 bits - size of bus


未对齐的访问有时会重叠两条高速缓存行,这将需要读取一个全新的缓存以获得数据。

此外,CPU的某些部分将不得不站起来将这两个不同的缓存行中的单个对象放在一起。一条数据。一方面,它将是非常高的位,而另一方面,是非常低的位。

将完全集成到管道中的专用硬件可以处理将对齐的对象移动到必要的对象上CPU数据总线的第一个位,但是此类硬件可能缺少未对齐的对象,因为使用那些晶体管来加速正确优化的程序可能更有意义。

无论如何,第二个存储器读取不管有多少专用硬件(假想地和愚蠢地)专门用于修补未对齐的内存操作,有时有时这样做都会减慢管道的速度。

评论


不管有多少专用硬件(假设地和愚蠢地)专用于修补未对齐的内存操作-现代Intel CPU,请站起来并/ wave。 :P完全有效地处理未对齐的256位AVX负载(只要它们不超过高速缓存行边界)对于软件来说很方便。甚至拆分的负载也算不错,Skylake极大地改善了页面拆分的加载/存储的代价,从大约100个周期降低到大约10个周期。 (如果在未对齐的缓冲区上进行矢量化,而循环不会花费额外的启动/清除代码对齐指针,则会发生这种情况)

– Peter Cordes
8月19日8:53

在L1d缓存和加载/存储执行单元之间具有512位路径的AVX512 CPU的指针对齐确实会遭受更多的苦难,因为每次加载都是对齐的,而不是彼此对齐的。

– Peter Cordes
8月19日8:53

#5 楼

@joshperry对这个问题给出了很好的答案。除了他的回答,我还有一些数字以图形方式显示了所描述的效果,尤其是2X放大。这是指向Google电子表格的链接,显示了不同单词对齐方式的效果。
此外,这是指向Github要点的链接,其中包含测试代码。
测试代码改编自本文由乔纳森·伦茨(Jonathan Rentzsch)撰写,引用了@joshperry。这些测试是在Macbook Pro上运行的,该Macbook Pro具有2.8 GHz四核Intel Core i7 64位处理器和16GB RAM。



评论


x和y坐标是什么意思?

– Shuva
18-10-2在20:07



哪一代酷睿i7? (感谢您发布代码链接!)

–尼克·迪索尼尔(Nick Desaulniers)
19年1月7日在7:11

#6 楼

如果具有字节可寻址内存的系统具有32位宽的内存总线,则意味着实际上有四个字节宽的内存系统,所有这些系统都连接在一起以读取或写入相同的地址。对齐的32位读取将需要在所有四个存储系统中的相同地址中存储信息,因此所有系统都可以同时提供数据。不对齐的32位读取将需要某些存储系统从一个地址返回数据,而另一些则需要从下一个较高地址返回数据。尽管有些内存系统经过优化可以满足此类请求(除了它们的地址之外,它们实际上还具有“加一”信号,这使它们使用的地址比指定的地址高一个),但这种功能增加了可观的成本存储系统的复杂性;大多数商品存储系统根本无法同时返回不同32位字的部分。

#7 楼

如果您有32位数据总线,则连接到内存的地址总线地址线将从A2开始,因此在单个总线周期中只能访问32位对齐的地址。

因此,如果一个字跨越地址对齐边界-即16/32位数据的A0或32位数据的A1不为零,则需要两个总线周期来获取数据。

某些体系结构/指令集不支持未对齐的访问,并且会在此类尝试中生成异常,因此编译器生成的未对齐的访问代码不仅需要附加的总线周期,而且还需要附加的指令,从而使效率更低。

#8 楼

在PowerPC上,您可以毫无问题地从奇数地址加载整数。

Sparc和I86和(我认为)Itatnium会在您尝试此操作时引发硬件异常。

一个32位负载与四个8位负载不会在大多数现代处理器上产生很大的不同。数据是否已经在缓存中将产生更大的影响。

评论


在Sparc上,这是一个“公共汽车错误”,因此在彼得·范德林登(Peter Van der Linden)的“专家C编程:深刻的C秘密”一章中,“公共汽车错误,坐火车”一章。

– jjg
4月1日19:35

它说PowerPC可以处理32位未对齐数据会引发64位数据的硬件异常。

–严酷
8月21日10:44