内联函数如何编译为汇编?

How are inline functions compiled to assembly?

本文关键字:编译 汇编 函数 何编译      更新时间:2023-10-16

我有一些循环更新值的代码C++,出于好奇,我想看看构成主体循环的程序集。这让我对编译后的内联外观进行了一些实验(编译器是带有 O2 的 MSVC)。

但是,当我将指令集与我认为它实际内联时的样子进行比较时,我对我的发现有点困惑。以下是一些上下文:

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和 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循环生成的程序集时,它们有很大的不同;事实上,从a调用inlineMe的程序集指令是其两倍。如何弥合这种差距?

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)  

函数的内联有三个主要效果:

  • 它消除了函数调用开销。
  • 它允许编译器跨函数边界进行优化。
  • 它允许编译器对传递给函数的硬编码参数进行硬假设。这包括指向成员函数的 this 指针。

内联总是在C++代码转换为汇编之前进行。编译器实质上将内联函数视为被调用函数的源代码插入到调用位置。几乎。(实际上,编译器通常还会将内联函数编译为普通函数,并为其分配弱链接,但这不会在进一步的内联过程中使用。这里对此不感兴趣。

在您的示例中,aOwner的成员,b是堆栈上的局部变量。ab都保持状态v

要解决a,编译器需要通过 this 指针Owner来寻址它。为了解决b编译器不需要使用 this 指针Owner,它只是在堆栈上。仅此一项就已经在指令数量上产生了相当大的差异。实际上,这也取决于是否允许编译器内联callTheInline()以及编译器对Owner实例存储的了解。

a.v的值在函数callTheInline()结束之后持续存在,而b不会持续到函数结束之后。这可能允许编译器省略某些计算。但是b.v不会持续到函数的末尾之后,这允许编译器省略计算inlineMe()

他们不是。(尤其是当它们只是模板时。

它们在变成 asm 之前被内联(通常是根据编译器对数据流的内部表示,通常是某种 SSA)。之后会发生进一步的优化,因此实际的 asm 取决于它内联点的周围代码,当然还有参数和返回值的处理方式。

例如,具有一个调用站点中未使用的输出参数的函数可以优化计算它的函数部分。 或者,如果其中一个参数是编译时常量,则可以大大简化生成的asm。 (例如if(x<8)在内联和不断传播后可能会变得if(false)if(true)


在您的情况下,其中一个循环使用一个类成员对象,其指针可能指向任何位置。 您根本不使用ClassWithInline::v显示该函数,因此奇怪的是,它根本不是一个非静态成员函数,而不仅仅是一个模板化的免费函数。

但如果ClassWithInline::v真的进入其中,a.inlineMe(ptr + n);将涉及this.vthis.a.v,这可能指向也可能不指向重叠的记忆。 编译器不知道,因此必须做出保守的假设,或者在运行快速或安全版本之前发出 2 个版本的循环并检查重叠。 这将破坏自动矢量化,并且需要更多的存储/重新加载才能使asm即使在混叠的情况下也是正确的。

(这是一个struct,而不是class,所以这些成员是公共的,这个函数的调用者可能在调用我们之前修改了这些成员。

但是b.inlineMe(ptr + n)对两个指针都使用this.v,并且在内联后编译器可以看到这一点。

涉及的另一个内存来自new,已知它不与其他内存重叠。 即任何预先存在的指针都不能指向new[]返回的缓冲区。 我认为 MSVC 做了足够的别名分析来解决这个问题。 但鉴于缺乏自动矢量化,也许不是。


顺便说一句,将这两个指针都称为v,这使得思考/谈论变得非常混乱。