我有一些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循环生成的程序集时,它们有很大的不同。实际上,从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)
答案 0 :(得分:2)
函数内联具有三个主要作用:
内联总是在C ++代码转换为汇编之前发生。编译器实质上将内联函数视为被调用函数的源代码已插入到调用位置。几乎。 (实际上,编译器通常还 将内联函数编译为普通的普通函数并为其分配弱链接,但随后在进一步的内联过程中将不再使用它。在此不感兴趣。)
在您的示例中,a
是Owner
的成员,而b
是堆栈上的局部变量。 a
和b
都保持状态v
。
要寻址a
,编译器需要通过Owner
的this指针对其进行寻址。为了解决b
,编译器不需要使用Owner
的this指针,它只是在堆栈上。仅此一项就已经在指令数量上产生了很大的不同。实际上,这还取决于是否允许编译器内联callTheInline()
以及编译器对Owner
实例存储的了解。
a.v
的值在函数callTheInline()
的末尾持续存在,而b
在此函数的末尾不存在。这有可能使编译器省略某些计算。但是b.v
不会在函数的末尾持续存在,后者允许编译器省略计算inlineMe()
。
答案 1 :(得分:1)
不是。(尤其是当它们只是模板时。)
在变成asm之前先对其进行内联(通常是指编译器内部对数据流的表示形式,通常是某种SSA)。在之后进行了进一步的优化,因此实际的asm取决于内联代码在其内联的点,当然还有args和返回值的作用。
例如,在一个调用站点中未使用输出arg的函数可以优化计算该函数的函数部分。或者,如果其中一个args是一个编译时常量,则可以极大地简化生成的asm。 (例如,if(x<8)
可以在内联并不断传播后变成if(false)
或if(true)
。)
在您的情况下,循环之一使用类成员对象,其指针可能指向任何地方。您根本没有使用使用 ClassWithInline::v
来显示函数,因此很奇怪,它根本就是一个非静态成员函数,而不仅仅是模板化的自由函数。
但是如果加入ClassWithInline::v
,则a.inlineMe(ptr + n);
将涉及this.v
和this.a.v
,这可能会或可能不会导致内存重叠。编译器不知道,因此必须做出保守的假设,或者发出两个版本的循环并在运行快速或安全版本之前检查是否重叠。这将使自动向量化失败,并且需要更多的存储/重载才能使asm正确,即使在出现别名的情况下。
(这是struct
,而不是class
,因此这些成员是公开的,此函数的调用者可能在致电我们之前已修改了这些成员。)
但是b.inlineMe(ptr + n)
对两个指针都使用this.v
,在内联后,编译器可以看到。
涉及的其他内存来自new
,已知该内存不会与其他内存重叠。也就是说,任何先前存在的指针都不能指向new[]
返回的缓冲区。我认为MSVC可以进行足够的别名分析来解决此问题。但是,鉴于缺乏自动矢量化功能,也许没有。
顺便说一句,同时调用两个指针v
会使思考/讨论变得非常混乱。