是的,数据的对齐和排列都可以对性能产生很大的影响,不仅是百分之几,而且是百分之几到几百。
进行此循环,如果您运行了足够的循环,则两条指令很重要。
.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上对齐位边界,以及彼此之间相对相关的位置,您希望频繁使用的循环或重复使用的数据不要以相同的缓存方式降落,它们各自需要自己的缓存。编译器可以帮助例如对超标量体系结构的指令进行排序,重新排列彼此无关紧要的指令,
最大的疏忽是假设处理器是瓶颈。十年或更长时间以来,一直不是真的,向处理器供电是问题所在,这就是对齐性能下降,高速缓存抖动等问题发挥作用的地方。即使在源代码级别上只需做一点工作,在结构中重新排列数据,对变量/结构声明的排序,在源代码中对函数的排序以及一些用于对齐数据的额外代码,都可以将性能提高数倍。更多。