组装:REP MOV机制

Abu*_*nce 8 x86 assembly

查看以下汇编代码:

MOV ESI, DWORD PTR [EBP + C]
MOV ECX, EDI
MOV EAX, EAX
SHR ECX, 2
LEA EDI, DWORD PTR[EBX + 18]
REP MOVS DWORD PTR ES:[EDI], DWORD PTR [ESI]
MOV ECX, EAX
AND ECX, 3
REP MOVS BYTE PTR ES:[EDI], BYTE PTR[ESI]
Run Code Online (Sandbox Code Playgroud)

我得到代码摘录的书解释了第一个REP MOVS复制4字节块,第二个REP MOVS复制剩余的2字节块(如果存在).

REP MOVS说明书如何运作?根据MSDN,"该指令可以用REP作为前缀,重复操作ecx寄存器指定的次数." 难道不会一遍又一遍地重复同样的操作吗?

Jes*_*ter 15

有关特定指令的问题,请参阅指令集参考.

在这种情况下,您需要查找repmovs(不是mov).简而言之,rep重复以下字符串操作ecx时间.根据方向标志的设置movs将数据移动ds:esies:edi指针并递增或递减指针.因此,重复它会将一系列内存移动到其他地方.

PS:通常将操作大小编码为指令后缀,因此人们使用movsbmovsd指示bytedword操作.然而,一些编译器允许指定大小为你的榜样,通过byte ptrdword ptr.此外,操作数隐含在指令中,您无法修改它们.


Max*_*tin 5

简短的解释

在汇编代码级别,允许使用此指令的两种形式:"显式操作数"形式和"nooperand"形式.显式操作数形式允许使用符号显式指定存储器的源地址和目标地址.提供此显式操作数表单以允许文档; 但请注意,此表单提供的文档可能会产生误导.也就是说,符号不必指定正确的源和目标地址.源地址始终由DS指定:(RSI/ESI/SI),目标地址始终由ES:(RDI/EDI/DI)寄存器指定,必须在movsb执行指令之前正确加载.这就是我理解英特尔在这个问题上的官方立场.

很长的解释

REP MOVS DWORD PTR ES:[EDI], DWORD PTR [ESI]是...的同义词REP MOVSD; 并且REP MOVS BYTE PTR ES:[EDI], BYTE PTR[ESI]是...的同义词REP MOVSB.

根据数据大小,有以下MOVS命令:

  • MOVSB(字节,8位)
  • MOVSW(字,16位)
  • MOVSD(双字,32位)
  • MOVSQ(qword,64位) - 仅在64位模式下可用

MOVS命令将数据从DS:(SI/ESI/RSI)复制到ES:(DI/EDI/RDI) - SI/DI寄存器的大小基于您当前的模式 - 16位,32位或64位位.它还增加(减少)SI和DI寄存器(基于D标志,设置CLD以增加寄存器).

MOVS命令不能使用除SI/DI之外的其他寄存器,因此无需指定它们.

如果MOVS命令以REP为前缀,则重复执行以复制CX(ECX/RCX)字节数,减少CX,因此在结束时CX变为零.

自从1993年生产第一台奔腾CPU以来,英特尔开始使更简单的命令执行得更快,复杂的命令(如REP MOVS) - 更慢.

因此,REP MOVS变得非常缓慢,没有理由使用它.

2013年,英特尔决定重新访问REP MOVS.如果CPU(2013年之后生产)具有CPUID ERMSB(Encanced REP MOVSB)位,则rep movsb和rep stosb命令的执行方式与旧处理器不同,并且应该是快速的.在实践中,它仅对大块,256字节和更大的块快速,并且仅在满足某些条件时:

  • 源地址和目标地址都必须与16字节边界对齐(对于Ivy Bridge处理器,建议使用此边界大小,较新的边界可能更大,Cannonlake最多可达64字节);
  • 源区域不应与目标区域重叠;
  • 长度必须是64字节的倍数才能产生更高的性能;
  • 方向必须向前(CLD).

请参阅"英特尔优化手册"第3.7.6节"增强REP MOVSB和STOSB操作(ERMSB)" http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia- 32体系结构优化-手册.pdf

REP MOVS指令在小块上非常慢,因为启动成本约为35个周期.如果在循环中执行简单的MOV EAX,则没有启动成本,您可以在这35个周期内复制大量数据.

请注意,ERMSB为REP MOVSB产生最佳结果,而不是REP MOVSD(MOVSQ).所有REP MOVS指令变得更快,但REP MOVSB是最快的.

因此,您所显示的代码在没有ERMSB的处理器上不是最佳的(因为简单的MOV EAX副本会更快),或者使用ERMSB(因为只有MOVSB很快,而不是MOVSD,尽管差别不大).

您提供的代码可能只能在1985年发布的80386这样的旧处理器上获得最佳结果.

  • *所以,REP MOVS变得非常慢,并且没有理由使用它.*实际上,`rep movsd`在Pentium Pro中再次使用P6快速字符串.`rep movsB`只适用于Ivybridge中ERMSB的大型副本,但是`rep movsd`或`rep movsq`在之前的微体系结构中使用16B宽的加载/存储,并且是Linux内核在大多数CPU上使用的.有关Andy Glew(P6快速字符串的首席架构师)的一些评论,请参阅[此答案](/sf/answers/2373412121/). (3认同)
  • 我忘记了当 SSE 可用时 glibc 是否默认为“rep movsq”用于大副本;Linux 内核没有使用 SSE 的选项,并且在大多数 CPU 上,`rep movsd` / `q` 比带有常规 `mov rax, [mem]` 指令的复制循环要好得多。Linux 为大多数 x86 CPU 设置了 `rep_good`(这意味着它决定对 memcpy 使用 `rep`)http://elixir.free-electrons.com/linux/latest/source/arch/x86/lib/memcpy_64.S。 (2认同)
  • @PeterCordes,谢谢你的解释,当我在 2017 年 5 月 8 日写那篇文章时,我并不知道这一点。但现在我已经编辑了这篇文章,考虑到从那时起我获得的信息,也感谢你提出宝贵意见。 (2认同)