C语言 SSE 未对齐的内在负载是否比英特尔 CPU 上的内在负载慢x64_64



我正在考虑更改一些代码高性能代码,这些代码目前需要 16 字节对齐的数组并使用_mm_load_ps来放宽对齐约束并使用_mm_loadu_ps。关于 SSE 指令的内存对齐对性能的影响有很多神话,所以我做了一个小的测试用例,说明什么是内存带宽绑定循环。使用对齐或未对齐的加载内部函数,它通过一个大数组运行 100 次迭代,将元素与 SSE 内部函数相加。源代码就在这里。https://gist.github.com/rmcgibbo/7689820

配备Sandy Bridge Core i5的64位Macbook Pro的结果如下。数字越小表示性能越快。当我阅读结果时,我基本上没有看到在未对齐的内存上使用_mm_loadu_ps的性能损失。

我觉得这很令人惊讶。这是一个公平的测试/合理的结论吗?在哪些硬件平台上有区别?

$ gcc -O3 -msse aligned_vs_unaligned_load.c  && ./a.out  200000000
Array Size: 762.939 MB
Trial 1
_mm_load_ps with aligned memory:    0.175311
_mm_loadu_ps with aligned memory:   0.169709
_mm_loadu_ps with unaligned memory: 0.169904
Trial 2
_mm_load_ps with aligned memory:    0.169025
_mm_loadu_ps with aligned memory:   0.191656
_mm_loadu_ps with unaligned memory: 0.177688
Trial 3
_mm_load_ps with aligned memory:    0.182507
_mm_loadu_ps with aligned memory:   0.175914
_mm_loadu_ps with unaligned memory: 0.173419
Trial 4
_mm_load_ps with aligned memory:    0.181997
_mm_loadu_ps with aligned memory:   0.172688
_mm_loadu_ps with unaligned memory: 0.179133
Trial 5
_mm_load_ps with aligned memory:    0.180817
_mm_loadu_ps with aligned memory:   0.172168
_mm_loadu_ps with unaligned memory: 0.181852

结果中有很多噪音。 我在运行 Debian 7 的至强 E3-1230 V2 @ 3.30GHz 上重新运行了这个,在 200000000 个数组上进行了 12 次运行(丢弃第一次以考虑虚拟内存噪声(,在基准测试函数中对i进行了 10 次迭代,对您提供的函数进行了显式noinline,并且您的三个基准测试中的每一个都独立运行: https://gist.github.com/creichen/7690369

这是在 gcc 4.7.2 中。

noinline确保第一个基准测试没有被优化出来。

确切的调用是

./a.out 200000000 10 12 $n

02 $n.

以下是结果:

对齐load_ps

min:    0.040655
median: 0.040656
max:    0.040658

loadu_ps对齐

min:    0.040653
median: 0.040655
max:    0.040657

loadu_ps未对齐

min:    0.042349
median: 0.042351
max:    0.042352

如您所见,这些是一些非常严格的边界,表明loadu_ps在未对齐的访问上较慢(速度减慢约 5%(,但在对齐访问上则不然。 显然,在该特定计算机上,loadu_ps不会对对齐的内存访问进行任何惩罚。

查看程序集,load_ps版本和loadu_ps版本之间的唯一区别是后者包含movups指令,重新排序其他一些指令以进行补偿,并使用略有不同的寄存器名称。 后者可能完全无关紧要,前者可以在微码翻译期间得到优化。

现在,很难判断(不是可以访问更详细信息的英特尔工程师(movups指令是否/如何得到优化,但考虑到如果加载地址中的较低位为零,而未对齐的数据路径为零,则 CPU 芯片不会因简单地使用对齐的数据路径而付出很少的代价, 这对我来说似乎是合理的。

我在Core i7笔记本电脑上尝试了相同的方法,并得到了非常相似的结果。

总之,我想说的是,是的,您确实为未对齐的内存访问支付了罚款,但它足够小,可能会被其他影响淹没。 在您报告的运行中,似乎有足够的噪音来假设它对您来说也更慢(请注意,您应该忽略第一次运行,因为您的第一次试用将付出预热页表和缓存的代价。

这里有两个问题:给定相同的对齐地址,未对齐的载荷是否比对齐的载荷慢?具有未对齐地址的负载是否比具有对齐地址的加载慢?

较旧的英特尔 CPU(在这种情况下是几年前的"旧"(与使用新地址的对齐负载相比,使用具有对齐地址的未对齐负载指令确实有轻微的性能损失。较新的 CPU 往往没有此问题。

旧和较新的英特尔 CPU 都会因从未对齐的地址加载而受到性能损失,尤其是在高速缓存行越过时。

由于详细信息因处理器型号而异,因此您必须单独检查每个处理器的详细信息。

有时可以掩盖性能问题。用于测量的简单指令序列可能无法揭示未对齐负载指令使负载存储单元比对齐负载指令更繁忙,因此,如果在前一种情况下尝试某些附加操作,但在后一种情况下没有尝试,性能会下降。

请参阅英特尔® 64 和 IA-32 架构中的"§2.4.5.1 有效处理对齐危险"优化参考手册:

缓存和内存子系统处理每个工作负载中很大比例的指令。不同的地址对齐方案将对内存和缓存操作产生不同的性能影响。例如,L1 的 1 周期吞吐量(请参阅表 2-25(通常适用于来自 L1 缓存的自然对齐负载。但是,使用未对齐的负载指令(例如 MOVUPS、MOVUPD、MOVDQU 等(从 L1 访问数据将遇到不同的延迟,具体取决于特定的微架构和对齐场景。

我无法在此处复制该表,它基本上显示对齐和未对齐的 L1 负载为 1 个周期; 拆分缓存行边界为 ~4.5 个周期。

这依赖于架构,最近几代人已经显着改进了事情。另一方面,在较旧的Core2架构上:

$ gcc -O3 -fno-inline foo2.c -o a; ./a 1000000 
Array Size: 3.815 MB                    
Trial 1
_mm_load_ps with aligned memory:    0.003983
_mm_loadu_ps with aligned memory:   0.003889
_mm_loadu_ps with unaligned memory: 0.008085
Trial 2
_mm_load_ps with aligned memory:    0.002553
_mm_loadu_ps with aligned memory:   0.002567
_mm_loadu_ps with unaligned memory: 0.006444
Trial 3
_mm_load_ps with aligned memory:    0.002557
_mm_loadu_ps with aligned memory:   0.002552
_mm_loadu_ps with unaligned memory: 0.006430
Trial 4
_mm_load_ps with aligned memory:    0.002563
_mm_loadu_ps with aligned memory:   0.002568
_mm_loadu_ps with unaligned memory: 0.006436
Trial 5
_mm_load_ps with aligned memory:    0.002543
_mm_loadu_ps with aligned memory:   0.002565
_mm_loadu_ps with unaligned memory: 0.006400

最新更新