x86 rep 指令在现代(流水线/超标量)处理器上的性能

Posted

技术标签:

【中文标题】x86 rep 指令在现代(流水线/超标量)处理器上的性能【英文标题】:Performance of x86 rep instructions on modern (pipelined/superscalar) processors 【发布时间】:2012-01-15 12:53:40 【问题描述】:

我最近一直在编写 x86 程序集(为了好玩),想知道以 rep 为前缀的字符串指令是否真的在现代处理器上具有性能优势,或者它们是否只是为了向后兼容而实现的。

我可以理解为什么英特尔最初会在处理器一次只运行一条指令时实现 rep 指令,但现在使用它们有什么好处吗?

通过编译成更多指令的循环,有更多指令要填满管道和/或乱序发出。现代处理器是为优化这些以 rep 为前缀的指令而构建的,还是在现代代码中很少使用 rep 指令,以至于它们对制造商来说并不重要?

【问题讨论】:

我已经有 5 年没有研究过这个了,但当时我的个人经验是,至少 rep movsd 和 rep stosd 比简单循环快,而一些扫描变体是不是。不过,从那以后,情况可能会发生重大变化。 在不同的处理器上进行测试,自己看看。 感谢您的意见,伙计们。 Alex:我可能最终会,但我没有很多不同的 proc 可以尝试,所以它只会在真正的 proc 上与没有管道的模拟器上进行。另外,我很懒,如果其他人可能已经做过,我宁愿不做这项工作。 :) 相关:lots of detail about x86 memory bandwidth,NT 存储与常规存储,以及单核如何无法始终使内存带宽饱和的内容(请参阅那里的答案中的“延迟绑定平台”)。还有一些rep movs / stos 与向量循环的比较。 【参考方案1】:

由于没有人给你任何数字,我将给你一些我通过对我的垃圾收集器进行基准测试发现的一些数据,该垃圾收集器非常依赖 memcpy。我要复制的对象有 60% 的长度为 16 个字节,其余 30% 的长度为 500 - 8000 字节左右。

前提条件:dstsrcn 都是 8 的倍数。 处理器:AMD Phenom(tm) II X6 1090T 处理器 64bit/linux

这是我的三个memcpy 变体:

手工编码的while循环:

if (n == 16) 
    *dst++ = *src++;
    *dst++ = *src++;
 else 
    size_t n_ptrs = n / sizeof(ptr);
    ptr *end = dst + n_ptrs;
    while (dst < end) 
        *dst++ = *src++;
    

ptruintptr_t 的别名)。时间:101.16%

rep movsb

if (n == 16) 
    *dst++ = *src++;
    *dst++ = *src++;
 else 
    asm volatile("cld\n\t"
                 "rep ; movsb"
                 : "=D" (dst), "=S" (src)
                 : "c" (n), "D" (dst), "S" (src)
                 : "memory");

时间:103.22%

rep movsq

if (n == 16) 
    *dst++ = *src++;
    *dst++ = *src++;
 else 
    size_t n_ptrs = n / sizeof(ptr);
    asm volatile("cld\n\t"
                 "rep ; movsq"
                 : "=D" (dst), "=S" (src)
                 : "c" (n_ptrs), "D" (dst), "S" (src)
                 : "memory");

时间:100.00%

req movsq 以微弱优势获胜。

【讨论】:

RCX 寄存器也被 REP MOVS 更改。 我们如何修复上述代码以声明对 CX 的更改? (声明将其设置为 0?) @CecilWard:Enhanced REP MOVSB for memcpy 为rsp movsb 保存了内联汇编。另一种选择是使用"+c"(n) 作为输入/输出操作数。如果您以后再也没有读取该 C 变量,编译器将有效地知道输入寄存器已被破坏。【参考方案2】:

除了 FrankH 的出色回答;我想指出,哪种方法最好还取决于字符串的长度、对齐方式以及长度是固定的还是可变的。

对于小字符串(可能最多约 16 个字节),使用简单指令手动执行可能更快,因为它避免了更复杂技术的设置成本(并且对于固定大小的字符串可以轻松展开)。对于中等大小的字符串(可能从 16 字节到 4 KiB),类似“REP MOVSD”(如果可能出现错位,则加入一些“MOVSB”指令)可能是最好的。

对于任何比这更大的东西,有些人会很想进入 SSE/AVX 和预取等。更好的想法是修复调用者/s,以便不需要复制(或 strlen() 或其他)首先。如果你足够努力,你几乎总能找到方法。 注意:也要非常警惕“假定的”快速 mempcy() 例程 - 通常它们已经在大量字符串上进行了测试,而不是在更可能的小/小/中字符串上进行了测试。

还要注意(为了优化而不是方便)由于所有这些差异(可能是长度、对齐方式、固定或可变大小、CPU 类型等),拥有一个多用途“memcpy()”的想法因为所有非常不同的情况都是近视的。

【讨论】:

确认。优化指南(包括 Intel/AMD 以及 Agner Fog 的材料和许多其他材料)也确实提到了这些事情;在许多情况下,一种策略: 1. 对于短字符串,内联原始指令 2. 对于中等大小,大操作数大小rep movs 3. 对于已知的大块,使用 SIMD 单元。并且始终对您的数据进行测试,因为如果您的大部分字符串都是 IIRC REP MOVSD 在现代硬件上通常REP MOVSB 慢得多。可能是因为现代 CPU 仅针对 REP MOVSB 进行了特殊优化,因为它的使用频率远高于 REP MOVSD @PaulGroke:可能有几个 CPU 的 rep movsb 优于 rep movsd,但大多数 CPU 也实现了 rep movsd / movsq 的所有 ERMSB 魔法。在 IvyBridge 的增强型 Rep MovSB 功能之前,rep movsb 在 Intel CPU 上通常更糟糕。请参阅 Enhanced REP MOVSB for memcpy,它有一个 excellent 答案,其中包含有关 x86 内存带宽的大量详细信息。【参考方案3】:

在 AMD 和 Intel 的优化指南中,此类问题都有很多篇幅。在这方面给出的建议的有效性有一个“半衰期”——不同的 CPU 代的行为不同,例如:

AMD Software Optimization Guide (Sep/2005),第 8.3 节,第 8 页。 167:在执行字符串操作时避免使用 REP 前缀,尤其是在复制内存块时。 AMD Software Optimization Guide (Apr/2011),第 9.3 节,第 页。 148:在执行字符串操作时明智地使用 REP 前缀

Intel Architecture Optimization Manual 在表 7-2 上给出了各种块复制技术(包括rep stosd)的性能比较数据。内存复制例程的相对性能,pg。 7-37f.,针对不同的 CPU,同样,在一个 CPU 上最快的可能在其他 CPU 上不是最快的。

在许多情况下,最近的 x86 CPU(具有“字符串”SSE4.2 操作)可以通过 SIMD 单元执行字符串操作,请参阅this investigation。

要跟进所有这些(和/或在不可避免的情况再次发生变化时让自己保持最新状态),请阅读Agner Fog's Optimization guides/blogs。

【讨论】:

rep movsrep stos 通常很好(对于中到大的对齐缓冲区),repe / repne scas / cmps 通常不好。 Re: SSE4.2:它们可能对strstr 或其他可以充分利用其全部功能的情况有用,但通常不适用于strcmpstrchr,因为它们比pcmpeqb 慢。 They're especially bad for memcmp 或显式长度字符串。

以上是关于x86 rep 指令在现代(流水线/超标量)处理器上的性能的主要内容,如果未能解决你的问题,请参考以下文章

计算机组成原理6-流水线多发射和超标量SIMD

ARM Cortex A9的流水线介绍

(计算机组成原理)第五章中央处理器-第五节2:指令流水线影响因素和分类及多发技术

计算机体系的划分

x86指令集都有哪些劣势

现代32位或64位x86汇编