我有一些循环更新值的代码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的成员,所有者有一个函数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调用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) 发布于 2019-02-19 18:54:12
功能的内衬有三大作用:
内联总是在将C++代码转换为程序集之前发生。编译器本质上将内联函数视为被调用函数的源代码插入到调用的位置。差不多了。(实际上,编译器通常也会将内联函数编译成普通函数,并将弱链接分配给它,但这在以后的内联过程中就不会用到了。这在这里没有意义。)
在您的示例中,a是Owner的成员,b是堆栈上的局部变量。a和b都维护状态v。
要寻址a,编译器需要通过Owner的这个指针来处理它。为了处理b,编译器不需要使用Owner的这个指针,它就在堆栈上。光是这一点就已经使指令的数量发生了很大的变化。实际上,这还取决于编译器是否允许内联callTheInline(),以及编译器对Owner实例的存储了解多少。
a.v的值在函数callTheInline()结束后仍然存在,而b在函数结束后不持久。这可能允许编译器省略某些计算。但是,b.v不会在函数结束后持久化,从而允许编译器省略计算inlineMe()。
发布于 2019-02-19 19:04:10
它们不是.(特别是当它们只是模板时)。
在转换成之前,它们是内联的(通常是编译器对数据流的内部表示,通常是某种SSA)。在此之后将进行更多的优化,因此实际的asm取决于其内联点的周围代码,当然还取决于args以及返回值的处理方式。
例如,在一个调用站点中未使用的带有输出arg的函数可以优化计算它的函数的部分。或者,如果其中一个arg是编译时常量,则可以极大地简化由此产生的asm。(例如,if(x<8)可以在内联和恒定传播后变成if(false)或if(true)。)
在您的例子中,一个循环使用一个类成员对象,其指针可能指向任何地方。您根本没有使用ClassWithInline::v显示这个函数,所以它是一个非静态成员函数,而不是一个模板化的免费函数,这是很奇怪的。
但是,如果ClassWithInline::v a.inlineMe(ptr + n); 确实参与其中,那么 a.inlineMe(ptr + n);将涉及 this.v 和 this.a.v,,它们可能指向或不指向重叠内存。编译器不知道,所以必须做出保守的假设,或者发出两个版本的循环,并在运行快速或安全版本之前检查是否重叠。这将击败自动矢量化,并且需要更多的存储/重新加载来使asm即使在混叠的情况下也是正确的。
(这是一个struct,而不是class,因此这些成员是公共的,该函数的调用方可能在调用我们之前修改了这些成员。)
但是b.inlineMe(ptr + n) 使用 this.v 作为两个指针,在内联之后编译器可以看到这一点。
涉及到的另一个内存来自new,它不与其他内存重叠。也就是说,任何预先存在的指针都不能指向new[]返回的缓冲区。我认为MSVC做了足够多的别名分析来解决这个问题。但考虑到缺乏自动矢量化,也许不会。
顺便说一句,调用两个指针( v )会使思考/谈论变得非常混乱。
https://stackoverflow.com/questions/54772936
复制相似问题