装配:REP MOVS 机构
Assembly: REP MOVS mechanism
查看以下汇编代码:
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]
我从中获得代码摘录的那本书解释了第一个 REP MOVS
复制 4 字节块,第二个 REP MOVS
复制剩余的 2 字节块(如果存在)。
REP MOVS
指令如何操作?根据MSDN,"The instruction can be prefixed by REP to repeat the operation the number of times specified by the ecx register."那不就是一遍又一遍地重复同样的操作吗?
有关特定指令的问题,请始终查阅指令集参考。
在这种情况下,您需要查找 rep
and movs
。
简而言之,rep
重复以下字符串操作 ecx
次。 movs
将数据从 ds:esi
复制到 es:edi
并根据方向标志的设置递增或递减指针 。因此,重复它会将一段内存移动到其他地方。
PS:通常将操作大小编码为指令后缀,所以人们用movsb
和movsd
来表示byte
或dword
操作.但是,某些汇编程序允许通过 byte ptr
或 dword ptr
指定大小,如您的示例所示。另外,操作数隐含在指令中,您不能修改它们。
关于语法的简短解释
在汇编代码级别,允许使用此指令的两种形式:“显式操作数”形式和“无操作数”形式。显式操作数形式允许使用符号显式指定内存的源地址和目标地址。提供这种显式操作数形式是为了允许记录;但是请注意,此表格提供的文件可能会产生误导。也就是说,符号不必指定正确的源地址和目标地址。源地址总是由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 年第一款 Pentium CPU 生产以来,Intel 开始让简单的命令执行得更快,而复杂的命令(如 REP MOVS)执行得更慢。因此,REP MOVS 变得非常慢,并且没有更多理由在基于 P5 微架构(1993-1997)的 Pentium CPUs 中使用它。
与 P5 微架构并行,Intel 开发了 P6 微架构,它决定重新访问 REP MOVS,并且自 1996 年以来,实施了“快速字符串”功能,使 REP MOVS 再次变快。
2013年,Intel决定再次重温REP MOVS,并实现了CPUID ERMSB (Enhanced REP MOVSB)位,本意是表示CPU实现了byte-sized move和以快速有效的方式存储指令(movsb、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-architectures-optimization-manual.pdf
REP MOVS 指令在小块上非常慢,因为启动成本约为 35 个周期。如果你在一个循环中做简单的 MOV EAX(或类似的东西),没有启动成本,你可以在这 35 个周期内复制大量数据。
请注意,ERMSB 为 REP MOVSB 产生最佳结果,而不是 REP MOVSD (MOVSQ)。所有 REP MOVS 指令都变得明显更快,但 REP MOVSB 是所有 ERMSB 指令中最快的。这与较旧的处理器(2013 年之前)形成对比,其中可用的最大 MOVS 大小(64 位上的 MOVSQ,32 位上的 MOVSD)产生最快的结果。
所以你展示的代码对于带有 ERMSB 的处理器来说不是最优的,因为只有 MOVSB 很快,MOVSD 不是,虽然差别不大,而且一个 REP MOVSB 应该足够了——它会导致启动第一次 REP MOVSD 和 REP MOVSB 只花费一次而不是两次。
但是,对于没有 ERMBS 的处理器,您的代码是可以的,但 1993 年发布的基于 P5 的奔腾处理器除外,在该处理器中,简单的 MOV EAX 复制(或使用更大的 x87 寄存器)在循环中会更快。您提供的代码也会在非常老的处理器(如 1985 年发布的 80386)上提供最佳结果。
查看以下汇编代码:
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]
我从中获得代码摘录的那本书解释了第一个 REP MOVS
复制 4 字节块,第二个 REP MOVS
复制剩余的 2 字节块(如果存在)。
REP MOVS
指令如何操作?根据MSDN,"The instruction can be prefixed by REP to repeat the operation the number of times specified by the ecx register."那不就是一遍又一遍地重复同样的操作吗?
有关特定指令的问题,请始终查阅指令集参考。
在这种情况下,您需要查找 rep
and movs
。
简而言之,rep
重复以下字符串操作 ecx
次。 movs
将数据从 ds:esi
复制到 es:edi
并根据方向标志的设置递增或递减指针 。因此,重复它会将一段内存移动到其他地方。
PS:通常将操作大小编码为指令后缀,所以人们用movsb
和movsd
来表示byte
或dword
操作.但是,某些汇编程序允许通过 byte ptr
或 dword ptr
指定大小,如您的示例所示。另外,操作数隐含在指令中,您不能修改它们。
关于语法的简短解释
在汇编代码级别,允许使用此指令的两种形式:“显式操作数”形式和“无操作数”形式。显式操作数形式允许使用符号显式指定内存的源地址和目标地址。提供这种显式操作数形式是为了允许记录;但是请注意,此表格提供的文件可能会产生误导。也就是说,符号不必指定正确的源地址和目标地址。源地址总是由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 年第一款 Pentium CPU 生产以来,Intel 开始让简单的命令执行得更快,而复杂的命令(如 REP MOVS)执行得更慢。因此,REP MOVS 变得非常慢,并且没有更多理由在基于 P5 微架构(1993-1997)的 Pentium CPUs 中使用它。
与 P5 微架构并行,Intel 开发了 P6 微架构,它决定重新访问 REP MOVS,并且自 1996 年以来,实施了“快速字符串”功能,使 REP MOVS 再次变快。
2013年,Intel决定再次重温REP MOVS,并实现了CPUID ERMSB (Enhanced REP MOVSB)位,本意是表示CPU实现了byte-sized move和以快速有效的方式存储指令(movsb、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-architectures-optimization-manual.pdf
REP MOVS 指令在小块上非常慢,因为启动成本约为 35 个周期。如果你在一个循环中做简单的 MOV EAX(或类似的东西),没有启动成本,你可以在这 35 个周期内复制大量数据。
请注意,ERMSB 为 REP MOVSB 产生最佳结果,而不是 REP MOVSD (MOVSQ)。所有 REP MOVS 指令都变得明显更快,但 REP MOVSB 是所有 ERMSB 指令中最快的。这与较旧的处理器(2013 年之前)形成对比,其中可用的最大 MOVS 大小(64 位上的 MOVSQ,32 位上的 MOVSD)产生最快的结果。
所以你展示的代码对于带有 ERMSB 的处理器来说不是最优的,因为只有 MOVSB 很快,MOVSD 不是,虽然差别不大,而且一个 REP MOVSB 应该足够了——它会导致启动第一次 REP MOVSD 和 REP MOVSB 只花费一次而不是两次。
但是,对于没有 ERMBS 的处理器,您的代码是可以的,但 1993 年发布的基于 P5 的奔腾处理器除外,在该处理器中,简单的 MOV EAX 复制(或使用更大的 x87 寄存器)在循环中会更快。您提供的代码也会在非常老的处理器(如 1985 年发布的 80386)上提供最佳结果。