编译器选择不使用 REP MOVSB 指令进行字节数组移动

c00*_*0fd 4 c++ x86 assembly compiler-optimization visual-c++

我正在检查使用最新版本的 VS 2017 C++ 编译器完成的项目的发布版本。我很好奇为什么编译器选择构建以下代码片段:

//ncbSzBuffDataUsed of type INT32

UINT8* pDst = (UINT8*)(pMXB + 1);
UINT8* pSrc = (UINT8*)pDPE;
for(size_t i = 0; i < (size_t)ncbSzBuffDataUsed; i++)
{
    pDst[i] = pSrc[i];
}
Run Code Online (Sandbox Code Playgroud)

像这样:

在此输入图像描述

        UINT8* pDst = (UINT8*)(pMXB + 1);
        UINT8* pSrc = (UINT8*)pDPE;
        for(size_t i = 0; i < (size_t)ncbSzBuffDataUsed; i++)
00007FF66441251E 4C 63 C2             movsxd      r8,edx  
00007FF664412521 4C 2B D1             sub         r10,rcx  
00007FF664412524 0F 1F 40 00          nop         dword ptr [rax]  
00007FF664412528 0F 1F 84 00 00 00 00 00 nop         dword ptr [rax+rax]  

00007FF664412530 41 0F B6 04 0A       movzx       eax,byte ptr [r10+rcx]  
        {
            pDst[i] = pSrc[i];
00007FF664412535 88 01                mov         byte ptr [rcx],al  
00007FF664412537 48 8D 49 01          lea         rcx,[rcx+1]  
00007FF66441253B 49 83 E8 01          sub         r8,1  
00007FF66441253F 75 EF                jne         _logDebugPrint_in_MainXchgBuffer+0A0h (07FF664412530h)  
        }
Run Code Online (Sandbox Code Playgroud)

与仅使用一条REP MOVSB指令相比?后者不是效率更高吗?

Pau*_*ers 5

编辑:首先,rep movsb彼得·科德斯告诉我们,这里有一个内在的速度会快得多,我相信他(我想我已经相信了)。如果您想强制编译器以这种方式执行操作,请参阅: https __movsb(): //learn.microsoft.com/en-us/cpp/intrinsics/movsb

至于为什么编译器没有为您执行此操作,在没有任何其他想法的情况下,答案可能是寄存器压力。要使用rep movsb编译器必须:

  • 设置rsi(=源地址)
  • 设置rdi(=目标地址)
  • 设置rcx(=计数)
  • 发出rep movsb

因此,现在它必须用完rep movsb指令规定的三个寄存器,并且它可能宁愿不这样做。具体而言rsi, 和rdi预计会在函数调用中保留,因此,如果编译器可以在任何特定函数的主体中使用它们,它就会(至少在首次进入该方法时)rcx保留this指针。

此外,通过我们看到编译器在那里生成的代码,r10rcx寄存器可能已经包含必需的源地址和目标地址(我们在您的示例中看不到),如果是这样,这对编译器来说将很方便。

在实践中,您可能会看到编译器在不同情况下做出不同的选择。请求的优化类型(/O1- 优化大小,vs /O2- 优化速度)也可能会影响这一点。

更多关于 x64 寄存器传递约定的信息请参见此处,更多关于 x64 ABI 的信息请参见此处


编辑2(再次受到Peter评论的启发):

编译器可能决定不对循环进行矢量化,因为它不知道指针是否对齐或可能重叠。如果没有看到更多代码,我们无法确定。但这与我的回答并不严格相关,考虑到OP实际询问的内容。