我正在检查使用最新版本的VS 2017 C ++编译器完成的项目的Release版本。我很好奇编译器为什么选择构建以下代码片段:
//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];
}
例如:
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)
}
相对于仅使用一条REP MOVSB
指令?后者会不会更有效率?
答案 0 :(得分:4)
编辑:首先,有一个rep movsb
的内在函数,彼得·科德斯(Peter Cordes)告诉我们这里会更快,而且我相信他(我想我已经做到了)。如果要强制编译器以这种方式执行操作,请参见:__movsb()
:https://docs.microsoft.com/en-us/cpp/intrinsics/movsb。
关于为什么编译器没有为您执行此操作,如果没有其他想法,答案可能是注册压力。要使用rep movsb
,编译器必须:
rsi
(=源地址)rdi
(=目标地址)rcx
(=计数)rep movsb
因此,现在它不得不用完rep movsb
指令授权的三个寄存器,并且它可能不希望这样做。具体而言,rsi
和rdi
有望在整个函数调用中保留,因此,如果编译器可以在任何特定函数的主体中使用它们,并且(在方法的初始输入时) ,至少)rcx
持有this
指针。
此外,使用我们看到的编译器生成的代码,r10
和rcx
寄存器可能已经包含必需的源地址和目标地址(我们无法从您的示例中看到) ,如果这样的话,对编译器来说很方便。
实际上,您可能会看到编译器在不同情况下做出不同选择。所请求的优化类型(/O1
-优化大小,而/O2
-优化速度)也可能会对此产生影响。
有关x64寄存器传递约定here和x64 ABI通常here的更多信息。
编辑2 (同样受到Peter的评论启发):
编译器可能决定不对循环进行矢量化处理,因为它不知道指针是否对齐或可能重叠。没有看到更多的代码,我们无法确定。但是,鉴于OP的实际要求,这与我的回答并不严格相关。
答案 1 :(得分:0)
这并不是真正的答案,我无法将其全部塞入评论中。我只想分享我的其他发现。 (这可能仅与Visual Studio编译器有关。)
与众不同的是您如何构造循环。例如:
假设以下结构定义:
#define PCALLBACK ULONG64
#pragma pack(push)
#pragma pack(1)
typedef struct {
ULONG64 ui0;
USHORT w0;
USHORT w1;
//Followed by:
// PCALLBACK[] 'array' - variable size array
}DPE;
#pragma pack(pop)
(1)构造for
循环的常规方法。在较大的序列化函数中间的某处调用以下代码块:
PCALLBACK* pDstClbks = (PCALLBACK*)(pDPE + 1);
for(size_t i = 0; i < (size_t)info.wNumCallbackFuncs; i++)
{
pDstClbks[i] = info.callbackFuncs[i];
}
正如本页答案中提到的,很明显,编译器因寄存器不足而产生了以下怪异现象(请参阅如何将rax
用作循环结束限制或{{1 }}可能被明确排除在循环之外的指令。)
movzx eax,word ptr [r13]
(2)因此,如果我将其重新编写为不太熟悉的C模式:
PCALLBACK* pDstClbks = (PCALLBACK*)(pDPE + 1);
00007FF7029327CF 48 83 C1 30 add rcx,30h
for(size_t i = 0; i < (size_t)info.wNumCallbackFuncs; i++)
00007FF7029327D3 66 41 3B 5D 00 cmp bx,word ptr [r13]
00007FF7029327D8 73 1F jae 07FF7029327F9h
00007FF7029327DA 4C 8B C1 mov r8,rcx
00007FF7029327DD 4C 2B F1 sub r14,rcx
{
pDstClbks[i] = info.callbackFuncs[i];
00007FF7029327E0 4B 8B 44 06 08 mov rax,qword ptr [r14+r8+8]
00007FF7029327E5 48 FF C3 inc rbx
00007FF7029327E8 49 89 00 mov qword ptr [r8],rax
00007FF7029327EB 4D 8D 40 08 lea r8,[r8+8]
00007FF7029327EF 41 0F B7 45 00 movzx eax,word ptr [r13]
00007FF7029327F4 48 3B D8 cmp rbx,rax
00007FF7029327F7 72 E7 jb 07FF7029327E0h
}
00007FF7029327F9 45 0F B7 C7 movzx r8d,r15w
这会产生更明智的机器代码(在同一编译器,同一函数,同一项目中):
PCALLBACK* pDstClbks = (PCALLBACK*)(pDPE + 1);
PCALLBACK* pEndDstClbks = pDstClbks + (size_t)info.wNumCallbackFuncs;
for(PCALLBACK* pScrClbks = info.callbackFuncs;
pDstClbks < pEndDstClbks;
pScrClbks++, pDstClbks++)
{
*pDstClbks = *pScrClbks;
}