Как встроенные функции компилируются в сборку? - PullRequest
0 голосов
/ 19 февраля 2019

У меня есть некоторый код C ++, который зацикливается для обновления значений, и из любопытства я хотел увидеть сборку, составляющую цикл тела.Это побудило меня немного поэкспериментировать с тем, как выглядит вставка после компиляции ( компилятор MSVC с O2 ).

Однако, когда я сравнил набор инструкций с тем, как я думал, что он должен выглядеть, когда он на самом деле встроен, я был немного смущен тем, что нашел.Вот некоторый контекст:

template<typename T>
struct ClassWithInline
{
    Values *v;

    ClassWithInline(Values *v) : v{ v } {}
    T inlineMe(T * const c) const
    {
        // some function of *c, using v->some_constants
    }
};

Объект Values - это просто нечто, содержащее константы.ClassWithInline является членом другого объекта, Owner, а владелец имеет функцию callTheInline:

struct Owner
{

    ClassWithInline<double> a;
    Values *v;

    Owner(Values *v) : a{ ClassWithInline<double>(v) }, v{ v } {}
    void callTheInline()
    {
        double *ptr = new double[100];
        double *dptr = new double[100];

        size_t the_end = std::floor(1000 + log(100000));

        for (size_t n = 0; n < the_end; ++n)
        {
            dptr[n] = a.inlineMe(ptr + n);
        }

        ClassWithInline<double> b(v);
        for (size_t n = 0; n < the_end; ++n)
        {
            dptr[n] = b.inlineMe(ptr + n);
        }
    }
};

(Номер итерации завершается так, что компилятор не знает размерцикл во время компиляции и внесение некоторых других оптимизаций.)

Теперь, когда я смотрю на сборку, созданную для циклов for, они резко отличаются;фактически тот, который вызывает inlineMe из a, имеет вдвое больше инструкций по сборке.Как мне преодолеть это неравенство?

a.inlineMe(ptr + n);

000000013F642094  mov         rbp,rbx  
000000013F642097  mov         qword ptr [rsp+20h],r15  
000000013F64209C  sub         rbp,rsi  
000000013F64209F  lea         r15,[r9-3]  
000000013F6420A3  mov         r14,rsi  
000000013F6420A6  lea         r10,[rbx+8]  
000000013F6420AA  sub         r14,rbx  
000000013F6420AD  nop         dword ptr [rax]  
000000013F6420B0  mov         rcx,qword ptr [rdi]  
000000013F6420B3  lea         rdx,[r14+r10]  
000000013F6420B7  movsd       xmm0,mmword ptr [r10-8]  
000000013F6420BD  movsd       xmm1,mmword ptr [rdx+rbp-10h]  
000000013F6420C3  addsd       xmm1,mmword ptr [r10]  
000000013F6420C8  movsd       xmm2,mmword ptr [rdi+8]  
000000013F6420CD  lea         rax,[rcx+r8]  
000000013F6420D1  mulsd       xmm0,xmm3  
000000013F6420D5  mulsd       xmm2,xmm2  
000000013F6420D9  addsd       xmm1,mmword ptr [rbx+rax*8]  
000000013F6420DE  mov         rax,r8  
000000013F6420E1  sub         rax,rcx  
000000013F6420E4  addsd       xmm1,mmword ptr [rbx+rax*8]  
000000013F6420E9  subsd       xmm1,xmm0  
000000013F6420ED  divsd       xmm1,xmm2  
000000013F6420F1  movsd       mmword ptr [r14+r10-8],xmm1  
000000013F6420F8  movsd       xmm1,mmword ptr [r10+8]  
000000013F6420FE  addsd       xmm1,mmword ptr [r10-8]  
000000013F642104  mov         rcx,qword ptr [rdi]  
000000013F642107  movsd       xmm0,mmword ptr [r10]  
000000013F64210C  movsd       xmm2,mmword ptr [rdi+8]  
000000013F642111  mulsd       xmm0,xmm3  
000000013F642115  lea         rax,[rcx+r8]  
000000013F642119  mulsd       xmm2,xmm2  
000000013F64211D  addsd       xmm1,mmword ptr [rbx+rax*8+8]  
000000013F642123  mov         rax,r8  
000000013F642126  sub         rax,rcx  
000000013F642129  addsd       xmm1,mmword ptr [rbx+rax*8+8]  
000000013F64212F  subsd       xmm1,xmm0  
000000013F642133  divsd       xmm1,xmm2  
000000013F642137  movsd       mmword ptr [rdx],xmm1  
000000013F64213B  movsd       xmm1,mmword ptr [r10+10h]  
000000013F642141  addsd       xmm1,mmword ptr [r10]  
000000013F642146  mov         rcx,qword ptr [rdi]  
000000013F642149  movsd       xmm0,mmword ptr [r10+8]  
000000013F64214F  movsd       xmm2,mmword ptr [rdi+8]  
000000013F642154  mulsd       xmm0,xmm3  
000000013F642158  lea         rax,[rcx+r8]  
000000013F64215C  mulsd       xmm2,xmm2  
000000013F642160  addsd       xmm1,mmword ptr [rbx+rax*8+10h]  
000000013F642166  mov         rax,r8  
000000013F642169  sub         rax,rcx  
000000013F64216C  addsd       xmm1,mmword ptr [rbx+rax*8+10h]  
000000013F642172  subsd       xmm1,xmm0  
000000013F642176  divsd       xmm1,xmm2  
000000013F64217A  movsd       mmword ptr [r14+r10+8],xmm1  
000000013F642181  movsd       xmm1,mmword ptr [r10+18h]  
000000013F642187  addsd       xmm1,mmword ptr [r10+8]  
000000013F64218D  mov         rcx,qword ptr [rdi]  
000000013F642190  movsd       xmm0,mmword ptr [r10+10h]  
000000013F642196  movsd       xmm2,mmword ptr [rdi+8]  
000000013F64219B  mulsd       xmm0,xmm3  
000000013F64219F  lea         rax,[rcx+r8]  
000000013F6421A3  mulsd       xmm2,xmm2  
000000013F6421A7  addsd       xmm1,mmword ptr [rbx+rax*8+18h]  
000000013F6421AD  mov         rax,r8  
000000013F6421B0  add         r8,4  
000000013F6421B4  sub         rax,rcx  
000000013F6421B7  addsd       xmm1,mmword ptr [rbx+rax*8+18h]  
000000013F6421BD  subsd       xmm1,xmm0  
000000013F6421C1  divsd       xmm1,xmm2  
000000013F6421C5  movsd       mmword ptr [r14+r10+10h],xmm1  
000000013F6421CC  add         r10,20h  
000000013F6421D0  cmp         r8,r15  
000000013F6421D3  jb          Owner::callTheInline+0B0h (013F6420B0h) 

b.inlineMe(ptr + n);

000000013F6422A4  movsd       xmm1,mmword ptr [rcx+r10*8-10h]  
000000013F6422AB  addsd       xmm1,mmword ptr [rdx+rcx]  
000000013F6422B0  movsd       xmm0,mmword ptr [rdx+rcx-8]  
000000013F6422B6  mulsd       xmm0,xmm3  
000000013F6422BA  addsd       xmm1,mmword ptr [rcx+r8*8-8]  
000000013F6422C1  addsd       xmm1,mmword ptr [rcx-8]  
000000013F6422C6  subsd       xmm1,xmm0  
000000013F6422CA  divsd       xmm1,xmm5  
000000013F6422CE  movsd       mmword ptr [rdi+rcx-8],xmm1  
000000013F6422D4  movsd       xmm2,mmword ptr [rdx+rcx-8]  
000000013F6422DA  addsd       xmm2,mmword ptr [rdx+rcx+8]  
000000013F6422E0  movsd       xmm0,mmword ptr [rdx+rcx]  
000000013F6422E5  mulsd       xmm0,xmm3  
000000013F6422E9  addsd       xmm2,mmword ptr [rcx+r8*8]  
000000013F6422EF  addsd       xmm2,mmword ptr [rcx]  
000000013F6422F3  subsd       xmm2,xmm0  
000000013F6422F7  divsd       xmm2,xmm5  
000000013F6422FB  movsd       mmword ptr [rdi+rcx],xmm2  
000000013F642300  movsd       xmm0,mmword ptr [rdx+rcx+8]  
000000013F642306  movsd       xmm1,mmword ptr [rdx+rcx]  
000000013F64230B  addsd       xmm1,mmword ptr [rcx+rbp]  
000000013F642310  mulsd       xmm0,xmm3  
000000013F642314  addsd       xmm1,mmword ptr [rcx+r8*8+8]  
000000013F64231B  addsd       xmm1,mmword ptr [rcx+8]  
000000013F642320  subsd       xmm1,xmm0  
000000013F642324  divsd       xmm1,xmm5  
000000013F642328  movsd       mmword ptr [rdi+rcx+8],xmm1  
000000013F64232E  movsd       xmm2,mmword ptr [rcx+r10*8+18h]  
000000013F642335  addsd       xmm2,mmword ptr [rdx+rcx+8]  
000000013F64233B  movsd       xmm0,mmword ptr [rcx+rbp]  
000000013F642340  mulsd       xmm0,xmm3  
000000013F642344  addsd       xmm2,mmword ptr [rcx+r8*8+10h]  
000000013F64234B  addsd       xmm2,mmword ptr [rcx+10h]  
000000013F642350  subsd       xmm2,xmm0  
000000013F642354  divsd       xmm2,xmm5  
000000013F642358  movsd       mmword ptr [r14+rcx],xmm2  
000000013F64235E  add         rcx,20h  
000000013F642362  sub         rax,1  
000000013F642366  jne         Owner::callTheInline+2A4h (013F6422A4h)  

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Это не так. (Особенно, когда они просто шаблоны.)

Они встроены до того, как превращаются в asm (обычно в терминахвнутреннее представление потока данных компилятором, часто своего рода SSA).Дальнейшая оптимизация происходит после , поэтому фактический asm зависит от окружающего кода в той точке, где он встроен, и, конечно, от аргументов и от того, что сделано с возвращаемым значением.

Функциянапример, с выходным аргументом, который не используется на одном сайте вызова, можно оптимизировать часть функции, которая его рассчитала.Или, если один из аргументов был константой времени компиляции, это могло бы значительно упростить получающийся ассм.(Например, if(x<8) может стать if(false) или if(true) после встраивания и постоянного распространения.)


В вашем случае один из ваших циклов использует объект-член класса, указатели которого могут указывать куда угодно.Вы вообще не показываете функцию , используя ClassWithInline::v, поэтому странно, что это нестатическая функция-член, а не просто шаблонная свободная функция.

Но если ClassWithInline::v входит в него, a.inlineMe(ptr + n); будет включать this.v и this.a.v, что может указывать или не указывать на перекрывающуюся память.Компилятор не знает, поэтому должен делать консервативные предположения или выдавать 2 версии цикла и проверять их на совпадение перед запуском быстрой или безопасной версии.Это бы победило автоматическую векторизацию и потребовало бы больше сохранения / перезагрузки, чтобы сделать asm корректным даже в случае псевдонимов.

(Это struct, а не class, поэтому эти члены являются открытыми иВызывающие эту функцию могли изменить эти члены перед вызовом.)

Но b.inlineMe(ptr + n) использует this.v для обоих указателей, и после вставки компилятор может увидеть это.

Другая задействованная память взята из new, которая, как известно, не пересекается с другой памятью.т.е. любые ранее существующие указатели не могут указывать в буфер, возвращаемый new[].Я думаю, что MSVC делает достаточно анализ псевдонимов, чтобы понять это.Но, учитывая отсутствие автоматической векторизации, возможно, нет.


Кстати, вызов обоих указателей v делает размышления / разговоры по-настоящему запутанными.

0 голосов
/ 19 февраля 2019

Встраивание функций имеет три основных эффекта:

  • Удаляет накладные расходы при вызове функции.
  • Позволяет компилятору оптимизировать через границы функции.
  • Это позволяет компилятору делать жесткие предположения о жестко закодированных параметрах, передаваемых функциям.Это включает указатель this на функции-члены.

Встраивание всегда происходит до того, как код C ++ транслируется в сборку.Компилятор по существу обрабатывает встроенную функцию так, как если бы исходный код вызываемой функции был вставлен в место вызова.Почти.(На самом деле компилятор обычно также компилирует встроенную функцию в обычную нормальную функцию и присваивает ей слабую связь, но тогда она не используется в дальнейшем процессе вставки. Это не представляет интереса.)

В вашем примере a является членом Owner, а b является локальной переменной в стеке.И a, и b поддерживают состояние v.

Для адреса a компилятор должен обратиться к нему через указатель this Owner.Для адреса b компилятору не нужно использовать указатель this Owner, он просто находится в стеке.Уже одно это существенно меняет количество инструкций.На самом деле это также зависит от того, разрешено ли компилятору встроить callTheInline() или нет, и что компилятор знает о хранилище экземпляра Owner.

Значение a.v сохраняется и после окончания функции callTheInline(), в то время как b не сохраняется после завершения этой функции.Это потенциально позволяет компилятору опускать определенные вычисления.Но b.v не сохраняется после конца функции, что позволяет компилятору пропускать вычисления inlineMe().

...