Выбор компилятором не использования инструкции REP MOVSB ​​для перемещения байтового массива - PullRequest
0 голосов
/ 01 июля 2018

Я проверяю сборку Release моего проекта, выполненную с помощью последней версии компилятора 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];
}

как таковой:

enter image description here

        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 инструкции? Разве последний не будет более эффективным?

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

Это не совсем ответ, и я не могу втиснуть все это в комментарий. Я просто хочу поделиться своими дополнительными выводами. (Это, вероятно, относится только к компиляторам 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 для предела конца цикла или инструкцию movzx eax,word ptr [r13], которая мог быть явно исключен из цикла.)

    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  

(2) Так что, если я переписываю его в менее знакомый шаблон C :

PCALLBACK* pDstClbks = (PCALLBACK*)(pDPE + 1);
PCALLBACK* pEndDstClbks = pDstClbks + (size_t)info.wNumCallbackFuncs;
for(PCALLBACK* pScrClbks = info.callbackFuncs; 
    pDstClbks < pEndDstClbks; 
    pScrClbks++, pDstClbks++)
{
    *pDstClbks = *pScrClbks;
}

это дает более разумный машинный код (на том же компиляторе, в той же функции, в том же проекте):

    PCALLBACK* pDstClbks = (PCALLBACK*)(pDPE + 1);
00007FF71D7E27C2 48 83 C1 30          add         rcx,30h  
    PCALLBACK* pEndDstClbks = pDstClbks + (size_t)info.wNumCallbackFuncs;
00007FF71D7E27C6 0F B7 86 88 00 00 00 movzx       eax,word ptr [rsi+88h]  
00007FF71D7E27CD 48 8D 14 C1          lea         rdx,[rcx+rax*8]  
    for(PCALLBACK* pScrClbks = info.callbackFuncs; pDstClbks < pEndDstClbks; pScrClbks++, pDstClbks++)
00007FF71D7E27D1 48 3B CA             cmp         rcx,rdx  
00007FF71D7E27D4 76 14                jbe         07FF71D7E27EAh
00007FF71D7E27D6 48 2B F1             sub         rsi,rcx  
    {
        *pDstClbks = *pScrClbks;
00007FF71D7E27D9 48 8B 44 0E 08       mov         rax,qword ptr [rsi+rcx+8]  
00007FF71D7E27DE 48 89 01             mov         qword ptr [rcx],rax  
00007FF71D7E27E1 48 83 C1 08          add         rcx,8  
00007FF71D7E27E5 48 3B CA             cmp         rcx,rdx  
00007FF71D7E27E8 77 EF                jb          07FF71D7E27D9h
    }

00007FF71D7E27EA 45 0F B7 C6          movzx       r8d,r14w  
0 голосов
/ 01 июля 2018

Редактировать: Во-первых, есть свойственный для rep movsb, который, как говорит нам Питер Кордес, будет гораздо быстрее, и я верю ему (я думаю, что я уже сделал). Если вы хотите заставить компилятор действовать таким образом, смотрите: __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 здесь , а о ABI x64 обычно здесь .


Редактировать 2 (снова вдохновлено комментариями Питера):

Компилятор, вероятно, решил не векторизовать цикл, потому что он не знает, совпадают ли указатели или могут перекрываться. Не видя больше кода, мы не можем быть уверены. Но это не совсем относится к моему ответу, учитывая то, о чем фактически спрашивал ОП.

...