【问题标题】:Is the SSE unaligned load intrinsic any slower than the aligned load intrinsic on x64_64 Intel CPUs?SSE 未对齐负载内在是否比 x64_64 Intel CPU 上的对齐负载内在慢?
【发布时间】:2013-12-14 02:40:47
【问题描述】:

我正在考虑更改一些当前需要 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

【问题讨论】:

  • 您是否认真地在 Python 中运行高性能代码? Python 比 C/C++ 慢 2 个数量级。至于未对齐的负载,由于 uArch 的更改,在使用较新的硬件 (Haswell) 时它们比以前更快。未对齐读/写的最大问题是它可能跨越缓存线边界甚至页面边界(当然不太常见)。
  • 在 python 中使用 scipy.weave 似乎是在不同机器上测试 C 的 sn-p 并发布到 stackoverflow 的最简单方法。我正在考虑更改的实际代码是一个包含在 python 中的 C 库。所有的计算都发生在 C 层。
  • 这些都不是在 Haswell 上。我在 Westmere 和 Sandy Bridge 上看到了 _mm_loadu_ps 与 _mm_load_ps 的轻微性能优势。
  • 现在,您正在根据对齐的参数测试 MOVUPS。看看当它实际用于未对齐移动时会发生什么。
  • 关于“问题文本变得相当长”,我建议暂时放弃所有 Python 内容,并将 Q 仅基于 C 代码。

标签: c performance sse


【解决方案1】:

这是依赖于架构的,最近几代人已经显着改进了一些东西。另一方面,在较旧的 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

【讨论】:

  • 此测试未能揭示 movups 的性能损失,即使是在对齐数据上使用 Core2。也许编译器看到数据保证对齐并使用movaps实现_mm_loadu_ps? (我没有看测试源)。无论如何,在 Core2 上,movups 负载的吞吐量只有一半,movups 商店的吞吐量只有 1/4。
【解决方案2】:

请参阅Intel® 64 and IA-32 Architectures Optimization Reference Manual 中的“§2.4.5.1 对齐危险的有效处理”:

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

这里的表格我不能复制,它基本上表明对齐和未对齐的L1负载是1个周期;分割缓存线边界约为 4.5 个周期。

【讨论】:

    【解决方案3】:

    您的结果中有很多噪音。我在运行 Debian 7 的 Xeon 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
    

    对于$n02

    结果如下:

    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_psloadu_ps 版本之间的唯一区别是后者包含了一条movups 指令,重新排序了一些其他指令以进行补偿,并且使用了稍微不同的寄存器名称。后者可能完全无关紧要,前者可以在微码翻译过程中得到优化。

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

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

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

    【讨论】:

      【解决方案4】:

      这里有两个问题:在相同对齐地址的情况下,非对齐加载是否比对齐加载慢?并且地址未对齐的加载是否比地址对齐的加载慢?

      与使用新地址的对齐加载相比,较旧的 Intel CPU(在这种情况下“较旧”只是几年前)在使用具有对齐地址的未对齐加载指令时确实有轻微的性能损失。较新的 CPU 往往没有这个问题。

      较旧和较新的 Intel CPU 在从未对齐的地址加载时都会产生性能损失,尤其是在跨越缓存行时。

      由于处理器型号的详细信息各不相同,因此您必须逐个检查以了解详细信息。

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

      【讨论】:

      • 自 Nehalem 以来的 Intel CPU 对于不跨越缓存线边界的未对齐加载/存储(几乎?)零惩罚。我认为在 Haswell 和后来的情况下它实际上为零,也许在 Nehalem 和 Sandybridge 上也是如此,但在那里不太确定。请参阅agner.org/optimizestackoverflow.com/tags/x86/info 中的其他链接。 (当然,没有 AVX 的未对齐加载会阻止您/编译器将加载折叠到 ALU 指令的内存操作数中,这会损害前端吞吐量)。商店转发可能在更多情况下适用于对齐的商店。
      • How can I accurately benchmark unaligned access speed on x86_64 详细介绍了一些细节:uop replay 来处理另一个缓存行,用于 Intel 上的缓存行拆分加载/存储。即使在高速缓存行 IIRC 内跨越 32 字节边界,AMD 也会出现减速。
      猜你喜欢
      • 2018-09-01
      • 2012-03-10
      • 1970-01-01
      • 2017-04-15
      • 2016-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多