我在 C++ 中将元素从一个数组复制到另一个数组。我rep movs
在 x86 中找到了似乎将 ESI 的数组复制到 EDI 大小为 ECX 的数组的指令。但是,我尝试的for
nor循环都没有编译为VS 2008 中的指令(在 Intel Xeon x64 处理器上)。如何编写将编译为该指令的代码?while
rep movs
6 回答
老实说,你不应该。REP 在指令集中是一种过时的保留,实际上非常慢,因为它必须调用 CPU 内部的微编码子例程,它具有 ROM 查找延迟并且也是非流水线的。
在几乎每个实现中,您都会发现memcpy()
编译器内在函数更易于使用且运行速度更快。
在 MSVC 下,有__movsxxx
&__stosxxx
内部函数将生成REP
前缀指令。
由于 crt 中的 sse2 分支,在 vc9+ 下还有一个“hack”来强制内在memset
aka REP STOS
,因为内在不再退出。这更好,__stosxxx
因为编译器可以针对常量对其进行优化并正确排序。
#define memset(mem,fill,size) memset((DWORD*)mem,((fill) << 24|(fill) << 16|(fill) << 8|(fill)),size)
__forceinline void memset(DWORD* pStart, unsigned long dwFill, size_t nSize)
{
//credits to Nepharius for finding this
DWORD* pLast = pStart + (nSize >> 2);
while(pStart < pLast)
*pStart++ = dwFill;
if((nSize &= 3) == 0)
return;
if(nSize == 3)
{
(((WORD*)pStart))[0] = WORD(dwFill);
(((BYTE*)pStart))[2] = BYTE(dwFill);
}
else if(nSize == 2)
(((WORD*)pStart))[0] = WORD(dwFill);
else
(((BYTE*)pStart))[0] = BYTE(dwFill);
}
当然REP
并不总是最好用的东西,imo你最好使用memcpy
它,它会分支到sse2或REPS MOV
基于你的系统(在msvc下),除非你想为“热”区域编写自定义程序集。 .
如果您确实需要该指令 - 使用内置汇编程序并手动编写该指令。您不能依赖编译器来生成任何特定的机器代码- 即使它在一次编译中发出它,它也可以决定在下一次编译期间发出一些其他等价物。
曾几何时,REP 和朋友们很好,当时 x86 CPU 是单流水线工业 CISC 处理器。
但这已经改变了。如今,当处理器遇到任何指令时,它首先会将其转换为更简单的格式(类似 VLIW 的微操作)并安排它以供将来执行(这是乱序执行的一部分,是不同的逻辑 CPU 内核,它可用于将写入后写入序列简化为单次写入等)。这种机制适用于翻译成一些类似 VLIW 的操作码的指令,但不适用于翻译成循环的机器码。循环翻译的机器代码可能会导致执行管道停止。
他们没有花费数十万个晶体管来构建 CPU 电路以处理执行管道中微操作的循环部分,而是以某种蹩脚的传统模式来处理它,这种模式会结结巴巴地停止管道,并要求现代程序员编写你自己的该死循环!
因此在机器编写代码时很少使用它。如果您在二进制可执行文件中遇到 REP,它可能是一个不知道更好的人工装配木偶,或者是一个真正需要它保存的几个字节来使用它而不是实际循环的破解者,它编写了它。
(但是。对我刚刚写的所有内容都持保留态度。也许这不再是真的了。我不再 100% 了解 x86 CPU 的内部结构,我开始从事其他爱好......)
我使用带有 cmps*、movs*、scas* 和 stos* 指令变体的 rep* 前缀变体来生成内联代码,从而最大限度地减少代码大小,避免不必要的调用/跳转,从而减少缓存完成的工作。另一种方法是设置参数并在其他地方调用 memset 或 memcpy,如果我想复制 100 个字节或更多字节,总体上可能会更快,但如果只是 10-20 个字节的问题,使用 rep 更快(或者至少是我最后一次测量)。
由于我的编译器允许规范和使用内联汇编函数,并在优化活动中包括它们的寄存器使用/修改,因此我可以在情况合适时使用它们。
在历史上 - 对制造商的策略没有任何洞察力 - 曾经有一段时间“rep movs *”(等)指令非常缓慢。我想那是在 Pentium/Pentium MMX 的时代。我的一位同事(比我更有洞察力)说,制造商减少了分配给代表处理的芯片面积(<=> 更少的晶体管/更多的微代码),并用它来更快地制作其他更常用的指令。
在十五年左右的时间里,rep 再次变得相对更快,这意味着更多的晶体管/更少的微码。