【问题标题】:Does rewriting memcpy/memcmp/... with SIMD instructions make sense?用 SIMD 指令重写 memcpy/memcmp/... 有意义吗?
【发布时间】:2011-07-16 08:04:21
【问题描述】:

用 SIMD 指令重写 memcpy/memcmp/... 在大型软件中有意义吗?

如果是这样,为什么 GCC 默认不为这些库函数生成 SIMD 指令?

另外,SIMD 是否可以改进其他功能?

【问题讨论】:

  • 这取决于您使用的操作系统和编译器库。例如。 Mac OS X 已经有了 SIMD 优化的 memcpy et al。此外,英特尔的 ICC 生成的内联 memcpy 比您可能在库中实现的任何东西都要快。
  • @Paul: memcpy 实际上是 SSE 内在函数的最坏情况,因为 SSE 不能用于边缘情况。这些编译器会为strlenmemchr 发出SIMD 代码吗?
  • @Ben:我刚刚检查了 ICC 12 - memcpy 和 strlen 都发出内联 SSE 代码,strchr 是一个库函数,看起来只是直接标量代码。

标签: performance sse simd


【解决方案1】:

是的,这些函数在使用 SSE 指令时要快得多。如果您的运行时库/编译器内部包含优化版本,那就太好了,但这似乎并不普遍。

我有一个自定义 SIMD memchr,它比库版本快得多。特别是当我找到 2 或 3 个字符中的第一个时(例如,我想知道这行文本中是否有一个等式,我搜索 =\n\r 中的第一个)。

另一方面,库函数经过了很好的测试,因此只有在您经常调用它们并且分析器显示它们占您 CPU 时间的很大一部分时才值得自己编写。

【讨论】:

  • SIMD memcpy 通常只会在 source 和/或 dest 已经在缓存中的副本中更快,因为几乎任何一半的 memcpy 都应该能够使可用的 DRAM 带宽饱和。
  • @Paul:SIMD总是更好。如果由于内存访问跟不上而严格来说它不是更快,那么该内核将被释放用于超线程、节能或推测性乱序执行。正如 Crashworks 所说,由于预取提示,SSE 还将更快地将数据提取到缓存中。如果没有 SSE,CPU 可能不得不在获取数据和进行复制之间交替进行,而 SSE 两者是并行发生的。
  • 在 memcpy et al 的情况下,执行线程中没有其他任何事情发生,因此没有任何好处。如果您的内核在等待 DRAM 访问时停滞不前,那么您无能为力 - DRAM 延迟可能达到 200 个时钟的数量级,这是很多无事可做的指令周期。
  • @Paul: (1) 并非所有的memcpy 调用都是针对数千字节的。您可能很容易在带有其他处理的循环内调用 memcpy 约 20 个字节。 (2) 现代 CPU 内核不限于处理来自单个线程的指令,因此我提到了超线程。 (3) 当读预取流水线化时,DRAM 延迟不太重要,只有吞吐量才是。 (4) 即使 DRAM 吞吐量阻碍了代码,最好还是高效地执行复制,因为 CPU 可以在同一时间内完成工作并且功耗更低(例如,动态降低时钟频率)
  • 你在使用什么糟糕的库,没有一个好的 SIMD memchr? Glibc 有手写 asm 版本的 memchr / strchr / memmove 等等,适用于 i386 和 x86-64(以及大多数其他 ISA),它们非常适合大缓冲区,并且许多具有良好的小缓冲区策略,也。 (通过动态链接器符号解析进行运行时调度,因此即使在没有-mavx2 编译的二进制文件中,它也可以在兼容的 CPU 上使用 AVX2)。您可以获得的主要好处是,如果您知道缓冲区已对齐和/或至少 16 个字节长,那么您可以避免分支来选择策略。
【解决方案2】:

可能没关系。 CPU 比内存带宽快得多,编译器的运行时库提供的memcpy 等的实现可能已经足够好了。在“大规模”软件中,你的性能不会被复制内存支配,无论如何(它可能被 I/O 支配)。

为了真正提高内存复制性能,一些系统有一个专门的DMA 实现,可用于从内存复制到内存。如果需要大幅提升性能,硬件是获得它的方法。

【讨论】:

  • 这在很大程度上取决于您是否使用了像 C++ iostreams 这样极其缓慢的 I/O API。很难以操作系统可以提供 I/O 的速度执行任何重要的处理。此外,由于各种原因,SIMD 速度更快,尤其是在设置 DMA 引擎的成本过高的较小块上。一方面,SSE 使用一组不同的 CPU 寄存器,因此您的工作变量保持注册状态,不会溢出到缓存中。
【解决方案3】:

这没有意义。你的编译器应该为 memcpy/memcmp/类似的内在函数隐式地发出这些指令,如果它能够发出 SIMD 的话。

您可能需要明确指示 GCC 使用 eg -msse -msse2 发出 SSE 操作码;一些 GCC 默认不启用它们。此外,如果你不告诉 GCC 进行优化(即-o2),它甚至不会尝试发出快速代码。

将 SIMD 操作码用于此类内存工作可能会对性能产生巨大影响,因为它们还包括缓存预取和其他对优化总线访问很重要的 DMA 提示。但这并不意味着您需要手动发出它们;尽管大多数编译器通常会发出 SIMD 操作,但我使用过的每个编译器都至少处理它们以用于基本的 CRT 内存功能。

基本数学函数也可以从将编译器设置为 SSE 模式中受益匪浅。 You can easily get an 8x speedup 基本 sqrt() 只需告诉编译器使用 SSE 操作码而不是可怕的旧 x87 FPU。

【讨论】:

  • 同意memcpy 最有可能得到适当优化。来自<string.h><memory.h> 的许多其他函数也受益匪浅,并且没有被编译器广泛优化。
  • @BenVoigt:GCC 并不总是内联库函数的好版本,但好的库有好的手写 asm。例如Why is this code 6.5x slower with optimizations enabled? 显示了 GCC 在 -O1 内联一个非常糟糕的 repne scasb strlen 或在 -O2 内内联一个复杂的 32 位一次 bithack 的情况,它没有利用 SSE2。该程序完全依赖strlen 对巨大缓冲区的性能,因此调用glibc 的优化版本对它来说是一个巨大的胜利。库和内联之间有很大的不同。
【解决方案4】:

在 x86 硬件上,乱序处理应该没什么大不了的。处理器将实现必要的 ILP 并尝试为 memcpy 发出每个周期的最大加载/存储操作数,无论是 SIMD 还是标量指令集。

【讨论】:

    猜你喜欢
    • 2021-06-29
    • 2014-06-20
    • 2020-11-26
    • 1970-01-01
    • 2022-01-20
    • 2010-10-03
    • 1970-01-01
    • 2015-06-05
    • 2016-08-28
    相关资源
    最近更新 更多