内联函数如何编译为汇编?
How are inline functions compiled to assembly?
我有一些循环更新值的代码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++代码转换为汇编之前进行。编译器实质上将内联函数视为被调用函数的源代码插入到调用位置。几乎。(实际上,编译器通常还会将内联函数编译为普通函数,并为其分配弱链接,但这不会在进一步的内联过程中使用。这里对此不感兴趣。
在您的示例中,a
是Owner
的成员,b
是堆栈上的局部变量。a
和b
都保持状态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.v
和this.a.v
,这可能指向也可能不指向重叠的记忆。 编译器不知道,因此必须做出保守的假设,或者在运行快速或安全版本之前发出 2 个版本的循环并检查重叠。 这将破坏自动矢量化,并且需要更多的存储/重新加载才能使asm即使在混叠的情况下也是正确的。
(这是一个struct
,而不是class
,所以这些成员是公共的,这个函数的调用者可能在调用我们之前修改了这些成员。
但是b.inlineMe(ptr + n)
对两个指针都使用this.v
,并且在内联后编译器可以看到这一点。
涉及的另一个内存来自new
,已知它不与其他内存重叠。 即任何预先存在的指针都不能指向new[]
返回的缓冲区。 我认为 MSVC 做了足够的别名分析来解决这个问题。 但鉴于缺乏自动矢量化,也许不是。
顺便说一句,将这两个指针都称为v
,这使得思考/谈论变得非常混乱。
- 二叉排序树无法编译
- 编译时未启用intel oneApi CUDA支持
- 在VS代码中交叉编译Windows与Linux上的MinGW的SDL程序
- 编译包含字符串的代码时遇到问题
- C++汇编代码分析(使用 clang 编译)
- 设置Qt Creator以链接和编译汇编代码
- 如何将 WebAssembly 编译成常规汇编/本机代码(或 Cpp 等)
- 用MASM编译汇编文件时,缺少表达式操作员
- 如何将C++11代码编译为网络汇编
- 内联函数如何编译为汇编?
- MFC 反编译为任何非汇编代码
- 如何在linux下编译c/cpp代码,给出经过预处理的汇编文件和编译后的目标文件
- VS2005 中的 ffmpeg 编译 mathops.h 中的汇编代码无法编译
- 用汇编器消息编译错误
- 编译c++代码成汇编,然后反汇编
- 编译托管c++汇编针对特定的.net版本(VS2008)
- BFD_RELOC_64:用c++在32位linux上编译汇编指令
- 将c++编译成汇编能提供性能吗?
- 汇编器消息:编译C++时没有这样的指令
- 内联汇编代码不能在Visual c++ 2010 Express中编译