我想知道编译器在优化结构分配方面做得如何,我做了一些测试,结果令人惊讶。(我想向 GCC 提出这个问题。)
特别是我正在查看 GCC 4.6.3 (-O3) 的汇编程序(编辑:我还包括 GCC 4.8.0 的输出)产生以下代码:
template<class T>
struct C
{
T F[3];
C(){}
C(const T& t0,const T& t1,const T& t2): F{{t0},{t1},{t2}} {}
};
template<class T>
__attribute__((always_inline))
C<T>
operator+( const C<T>& l , const C<T>& r )
{
return C<T>( l.F[0] + r.F[0] ,
l.F[1] + r.F[1] ,
l.F[2] + r.F[2] );
}
int main()
{
C<C<C<float> > > a,b,c;
a=b+c;
printf("%s",(const char*)&a);
}
这里是汇编程序:
movss 264(%rsp), %xmm0
leaq 48(%rsp), %rbx
leaq 156(%rsp), %rbp
addss 376(%rsp), %xmm0
movss %xmm0, 4(%rsp)
movss 260(%rsp), %xmm0
addss 372(%rsp), %xmm0
movss %xmm0, 8(%rsp)
movss 256(%rsp), %xmm0
addss 368(%rsp), %xmm0
movss %xmm0, 12(%rsp)
movss 252(%rsp), %xmm0
addss 364(%rsp), %xmm0
movss %xmm0, 16(%rsp)
movss 248(%rsp), %xmm0
addss 360(%rsp), %xmm0
movss %xmm0, 20(%rsp)
movss 244(%rsp), %xmm0
addss 356(%rsp), %xmm0
movss %xmm0, 24(%rsp)
movss 240(%rsp), %xmm0
addss 352(%rsp), %xmm0
movss %xmm0, 28(%rsp)
movss 236(%rsp), %xmm0
addss 348(%rsp), %xmm0
movss %xmm0, 32(%rsp)
movss 232(%rsp), %xmm0
addss 344(%rsp), %xmm0
movss %xmm0, 36(%rsp)
movss 228(%rsp), %xmm0
addss 340(%rsp), %xmm0
movss %xmm0, 40(%rsp)
movss 224(%rsp), %xmm0
addss 336(%rsp), %xmm0
movss %xmm0, 44(%rsp) // 11-th
movss 220(%rsp), %xmm0
movss 164(%rsp), %xmm14
movss 160(%rsp), %xmm15
addss 332(%rsp), %xmm0
addss 272(%rsp), %xmm15
movss 216(%rsp), %xmm1
addss 276(%rsp), %xmm14
movss 212(%rsp), %xmm2
movss 208(%rsp), %xmm3
addss 328(%rsp), %xmm1
movss 204(%rsp), %xmm4
addss 324(%rsp), %xmm2
movss 200(%rsp), %xmm5
addss 320(%rsp), %xmm3
movss 196(%rsp), %xmm6
addss 316(%rsp), %xmm4
movss 192(%rsp), %xmm7
addss 312(%rsp), %xmm5
movss 188(%rsp), %xmm8
addss 308(%rsp), %xmm6
movss 184(%rsp), %xmm9
addss 304(%rsp), %xmm7
movss 180(%rsp), %xmm10
addss 300(%rsp), %xmm8
movss 176(%rsp), %xmm11
addss 296(%rsp), %xmm9
movss 172(%rsp), %xmm12
addss 292(%rsp), %xmm10
movss 168(%rsp), %xmm13
addss 288(%rsp), %xmm11
addss 284(%rsp), %xmm12
movss %xmm15, 384(%rsp)
addss 280(%rsp), %xmm13 // all 27 floats added
movss %xmm14, 388(%rsp)
movss %xmm0, 444(%rsp)
movss 44(%rsp), %xmm0
movss %xmm0, 448(%rsp)
movss 40(%rsp), %xmm0
movss %xmm0, 452(%rsp)
movss 36(%rsp), %xmm0
movss %xmm0, 456(%rsp)
movss 32(%rsp), %xmm0
movss %xmm0, 460(%rsp)
movss 28(%rsp), %xmm0
movss %xmm0, 464(%rsp)
movss 24(%rsp), %xmm0
movss %xmm0, 468(%rsp)
movss 20(%rsp), %xmm0
movss %xmm0, 472(%rsp)
movss 16(%rsp), %xmm0
movss %xmm0, 476(%rsp)
movss 12(%rsp), %xmm0
movss %xmm0, 480(%rsp)
movss 8(%rsp), %xmm0
movss %xmm13, 392(%rsp)
movss %xmm12, 396(%rsp)
movss %xmm11, 400(%rsp)
movss %xmm10, 404(%rsp)
movss %xmm9, 408(%rsp)
movss %xmm8, 412(%rsp)
movss %xmm7, 416(%rsp)
movss %xmm6, 420(%rsp)
movss %xmm5, 424(%rsp)
movss %xmm4, 428(%rsp)
movss %xmm3, 432(%rsp)
movss %xmm2, 436(%rsp)
movss %xmm1, 440(%rsp)
movss %xmm0, 484(%rsp) // Storing of temporary finished
movq 384(%rsp), %rax // Now begin copy temporary to destination
movss 4(%rsp), %xmm0
movss %xmm0, 488(%rsp)
movq %rax, 48(%rsp)
movq 392(%rsp), %rax
movq %rax, 56(%rsp)
movq 400(%rsp), %rax
movq %rax, 64(%rsp)
movq 408(%rsp), %rax
movq %rax, 72(%rsp)
movq 416(%rsp), %rax
movq %rax, 80(%rsp)
movq 424(%rsp), %rax
movq %rax, 88(%rsp)
movq 432(%rsp), %rax
movq %rax, 96(%rsp)
movq 440(%rsp), %rax
movq %rax, 104(%rsp)
movq 448(%rsp), %rax
movq %rax, 112(%rsp)
movq 456(%rsp), %rax
movq %rax, 120(%rsp)
movq 464(%rsp), %rax
movq %rax, 128(%rsp)
movq 472(%rsp), %rax
movq %rax, 136(%rsp)
movq 480(%rsp), %rax
movq %rax, 144(%rsp)
movl 488(%rsp), %eax
movl %eax, 152(%rsp) // Whole struct copied
.p2align 4,,10
汇编器从指示的行开始显示整个(嵌套)结构的附加副本。有人可能会说这是因为编译器必须发出赋值(与可以省略的副本相反)。
我重复了这个实验,这次我只使用了 2 倍嵌套结构,而不是 3 倍嵌套结构。然后不会发生额外的副本。这是第二个实验的代码:
int main()
{
C<C<float> > a,b,c;
a=b+c;
printf("%s",(const char*)&a);
}
和汇编程序:
movss 80(%rsp), %xmm0
movq %rsp, %rdx
movss 76(%rsp), %xmm1
addss 128(%rsp), %xmm0
movss 72(%rsp), %xmm2
addss 124(%rsp), %xmm1
movss 68(%rsp), %xmm3
addss 120(%rsp), %xmm2
movss 64(%rsp), %xmm4
addss 116(%rsp), %xmm3
movss 60(%rsp), %xmm5
addss 112(%rsp), %xmm4
movss 56(%rsp), %xmm6
addss 108(%rsp), %xmm5
movss 52(%rsp), %xmm7
addss 104(%rsp), %xmm6
movss 48(%rsp), %xmm8
addss 100(%rsp), %xmm7
addss 96(%rsp), %xmm8
xorl %eax, %eax
movss %xmm2, 24(%rsp)
movss %xmm3, 20(%rsp)
movss %xmm4, 16(%rsp)
movss %xmm5, 12(%rsp)
movss %xmm6, 8(%rsp)
movss %xmm7, 4(%rsp)
movss %xmm8, (%rsp)
movss %xmm1, 28(%rsp)
movss %xmm0, 32(%rsp)
很明显,临时结构(保存 的结果b+c
)可以完全分配在寄存器文件(9 个寄存器,xmm0-xmm8)中,随后无需复制即可存储。
然而,操作(加法+赋值)仅对存储容器上操作的指令造成弱数据依赖性。我要问的问题是:在第一个实验中,由于数据依赖性将允许指令的最佳调度,为什么编译器不以不需要额外副本的方式调度指令,即直接存储结果进入最终的内存地址?是否存在编译器无法查看的依赖性或边界的某些方面,从而有效地阻碍了这种优化?
编辑:
这里是从 GCC 4.8.0 (-std=c++0x -S -O3) 生成的汇编程序:
movss 264(%rsp), %xmm0
leaq 48(%rsp), %rdx
movss 260(%rsp), %xmm1
addss 376(%rsp), %xmm0
movss 256(%rsp), %xmm2
addss 372(%rsp), %xmm1
movss 252(%rsp), %xmm3
addss 368(%rsp), %xmm2
movss 248(%rsp), %xmm4
addss 364(%rsp), %xmm3
movss 244(%rsp), %xmm5
addss 360(%rsp), %xmm4
addss 356(%rsp), %xmm5
movss 196(%rsp), %xmm11
addss 308(%rsp), %xmm11
movss %xmm0, 4(%rsp)
movss %xmm1, 8(%rsp)
movss %xmm2, 12(%rsp)
movss %xmm3, 16(%rsp)
movss %xmm4, 20(%rsp)
movss %xmm5, 24(%rsp)
movss 240(%rsp), %xmm0
movss 236(%rsp), %xmm1
movss 232(%rsp), %xmm2
addss 352(%rsp), %xmm0
movss 228(%rsp), %xmm3
addss 348(%rsp), %xmm1
movss 224(%rsp), %xmm4
addss 344(%rsp), %xmm2
movss 220(%rsp), %xmm5
addss 340(%rsp), %xmm3
movss 216(%rsp), %xmm6
addss 336(%rsp), %xmm4
movss 212(%rsp), %xmm7
addss 332(%rsp), %xmm5
movss 208(%rsp), %xmm8
addss 328(%rsp), %xmm6
movss 204(%rsp), %xmm9
addss 324(%rsp), %xmm7
movss 200(%rsp), %xmm10
addss 320(%rsp), %xmm8
addss 316(%rsp), %xmm9
addss 312(%rsp), %xmm10
movss %xmm11, 28(%rsp)
movss 192(%rsp), %xmm12
movss 188(%rsp), %xmm13
movss 184(%rsp), %xmm14
addss 304(%rsp), %xmm12
movss 180(%rsp), %xmm15
addss 300(%rsp), %xmm13
addss 296(%rsp), %xmm14
movss 176(%rsp), %xmm11
addss 292(%rsp), %xmm15
addss 288(%rsp), %xmm11
movss %xmm12, 32(%rsp)
movss %xmm13, 36(%rsp)
movss %xmm14, 40(%rsp)
movss %xmm15, 44(%rsp)
movss 172(%rsp), %xmm12
movss 168(%rsp), %xmm13
movss 164(%rsp), %xmm14
addss 284(%rsp), %xmm12
movss 160(%rsp), %xmm15
addss 280(%rsp), %xmm13
addss 276(%rsp), %xmm14
movss %xmm11, 400(%rsp)
addss 272(%rsp), %xmm15
movss %xmm12, 396(%rsp)
movss %xmm13, 392(%rsp)
movss %xmm14, 388(%rsp)
movss 36(%rsp), %xmm13
movss 40(%rsp), %xmm14
movss %xmm15, 384(%rsp)
movss 32(%rsp), %xmm12
movss 44(%rsp), %xmm15
movss %xmm15, 404(%rsp)
movss %xmm14, 408(%rsp)
movss %xmm13, 412(%rsp)
movss %xmm12, 416(%rsp)
movq 384(%rsp), %rax
movss 28(%rsp), %xmm11
movss %xmm11, 420(%rsp)
movq %rax, 48(%rsp)
movq 392(%rsp), %rax
movss %xmm5, 444(%rsp)
movss %xmm4, 448(%rsp)
movss 24(%rsp), %xmm5
movq %rax, 56(%rsp)
movss %xmm3, 452(%rsp)
movq 400(%rsp), %rax
movss 20(%rsp), %xmm4
movss 16(%rsp), %xmm3
movss %xmm2, 456(%rsp)
movq %rax, 64(%rsp)
movq 408(%rsp), %rax
movss %xmm1, 460(%rsp)
movss 12(%rsp), %xmm2
movss 8(%rsp), %xmm1
movq %rax, 72(%rsp)
movq 416(%rsp), %rax
movss %xmm0, 464(%rsp)
movss 4(%rsp), %xmm0
movss %xmm10, 424(%rsp)
movss %xmm9, 428(%rsp)
movss %xmm8, 432(%rsp)
movss %xmm7, 436(%rsp)
movss %xmm6, 440(%rsp)
movss %xmm5, 468(%rsp)
movss %xmm4, 472(%rsp)
movss %xmm3, 476(%rsp)
movss %xmm2, 480(%rsp)
movss %xmm1, 484(%rsp)
movss %xmm0, 488(%rsp)
movq %rax, 80(%rsp)
movq 424(%rsp), %rax
movq %rax, 88(%rsp)
movq 432(%rsp), %rax
movq %rax, 96(%rsp)
movq 440(%rsp), %rax
movq %rax, 104(%rsp)
movq 448(%rsp), %rax
movq %rax, 112(%rsp)
movq 456(%rsp), %rax
movq %rax, 120(%rsp)
movq 464(%rsp), %rax
movq %rax, 128(%rsp)
movq 472(%rsp), %rax
movq %rax, 136(%rsp)
movq 480(%rsp), %rax
movq %rax, 144(%rsp)
movl 488(%rsp), %eax
movl %eax, 152(%rsp)
附加副本仍然存在。