关于C ++复制ELISION的悲伤真相

2021-04-03 20:21:12

复制ELISION是一个C ++编译器优化,因为它的名称表明,消除了胸部和移动操作。它类似于经典的副作复优化,但专门对可能对普通复制和移动构造函数的C ++对象执行。在这篇文章中,我会演练一个一个明显优化的一个例子,你可能期望的来自你的编译器实际上并没有在实践中发生。

假设您有一个长期函数调用返回一个对象,并且您希望立即将该对象传递给另一个函数,likethis:

#include< string> #include< string_view> //复制的某些类型,不易于摧毁,廉价但是//不自由移动。 struct widget {std :: string s;}; void消费(窗口小部件w);窗口小部件dosomeverycomplicationthingwithseveralguments(int arg1,std :: string_view arg2); void somefunction(){cenume(dosomeverycomplicationthingwithseveralarguments(123," hello"));}

某种功能():#@somefunction()pushq%rbx子q $ 32,%rsp movq%rsp,%rbx movl $ 5,%edx movl $ .l.str,%ECX MOVIQ%RBX,%RDI MOVL $ 123,%ESI CallQ DosomeVeryComplationTiptthingwithseveralguments (int,std :: basic_string_view< char,std :: char_traits< char>)movq%RBX,%RDI CallQ消费(小部件)MOVIQ(%RSP),%RDI Leaq 16(%RSP),% rax cmpq%rax,%rdi je .lbb0_2 callq运算符删除(void *)。lbb0_2:addq $ 32,%rsp popq%rbx retq.l.str:.cr:.criz"你好"

我们的临时窗口小部件从DosomeVeryComplicationTiptthingWithSeverAlguments返回的初始空间构建,即某种方式为其分配,然后将堆栈空间通过直接消耗的部分来消耗,因为我们应该从学习之前学习参数通知。

现在,想象一下,您可以在某些功能中的单线中决定太长,或者您希望向DosomeVeryComplicationTiptthingWithSeverthuments提供有意义的名称,因此您更改了代码:

DemencurbenctionV2():#@ demenctionv2()PUSHQ%R15 PUSPQ%R14 PUSPQ%R12 PUSHQ%RBX子Q $ 72,%RSP LEVE 40(%RSP),%RDI MOVL $ 5,%EDX MOVL $ .l.str,%ECX MOVL $ 123,%ESI CallQ DosomeVeryComplicationTiptthingWithSeverAlguments(int,std :: basic_string_view< char,std :: char_traits< char>)Leaq 24(%RSP),%R12 MOVIQ%R12,8(%RSP)MOVQ 40( %RSP),%R14 MOVQ 48(%RSP),%RBX MOVQ%R12,%R15 CMPQ $ 16,%RBX JB .LBB1_4 TestQ%RBX,%RBX JS .LBB1_13 MOVE%RBX,%RDI Incq%RDI JS .LBB1_14 CallQ运算符新(无符号长)MOVQ%rax,%r15 movq%rax,8(%rsp)movq%RBX,24(%RSP).lBB1_4:TestQ%RBX,%RBX JE .LBB1_8 CMPQ $ 1,%RBX JNE。 LBB1_7 MOVB(%R14),%AL MOVB%AL,(%R15)JMP .LBB1_8.LBB1_7:MOVQ%R15,%RDI MOVIQ%R14,%RSI MOVIQ%RBX,%RDX CallQ Memcpy.LBB1_8:Movq%RBX, 16(%RSP)MOVB $ 0,(%R15,%RBX)LIAQ 8(%RSP),%RDI CallQ消耗(窗口小部件)MOVIQ 8(%RSP),%RDI CMPQ%R12,%RDI JE .LBB1_10 CallQ运算符删除(voi. D *)。LBB1_10:MOVQ 40(%RSP),%RDI LEAQ 56(%RSP),%rax cmpq%rax,%rdi je .lbb1_12 callq运算符删除(void *)。lbb1_12:addq $ 72,%rsp popq% RBX popq%r12 popq%r14 popq%r15 retq.lbb1_13:movl $ .l.l.l.l.l.2,%edi callq std :: __throw_length_error(char conten *)。lbb1_14:callq std :: __throw_bad_alloc()。l.str: .asciz"你好" .str.2:.sciz" basic_string :: _ m_create"

现在,我们将我们完美的良好的小部件,复杂的问题,AndCopy它进入了一个新的临时小部件,以作为第一个消耗的Argumentto。当我们完成后,我们必须销毁两个小部件:复杂的资源和未命名的临时小部件遍布消费。您可能希望编译器会优化某些功能()就像某种功能一样,但它不会。

某种功能():#@ demenctionv3()PUSHQ%R14 PUSPQ%RBX子Q $ 72,%RSP LEAQ 8(%RSP),%RDI MOVL $ 5,%EDX MOVL $ .l.str,%ECX MOVL $ 123,%ESI CallQ DosomeVeryComplationTiptthingWithSeverGumber (int,std :: basic_string_view< char,std :: char_traits< char>)Leaq 56(%RSP),%R14 MOVQ%R14,40(%RSP)MOVQ 8(%RSP),%rax% Leaq 24(%RSP),%RBX CMPQ%RBX,%rax JE .LBB1_1 MOVQ%rax,40(%rsp)MOVQ 24(%RSP),%rax movq%rax,56(%rsp)jmp .lbb1_3.lbb1_1 :MOVUPS(%rax),%xmm0 movup%xmm0,(%r14).lbb1_3:movq 16(%rsp),%rax movq%rax,48(%rsp)movq%RBX,8(%RSP)MOVIQ $ 0, 16(%RSP)MOVB $ 0,24(%RSP)LEAQ 40(%RSP),%RDI CallQ消耗(窗口小部件)MOVIQ 40(%RSP),%RDI CMPQ%R14,%RDI JE .LBB1_5 CallQ运算符删除(void *。 .asciz"你好"

我们仍有两个小部件,它只是临时参数消耗是移动的。我们的第一个版本的某种功能仍然更小,更快!

复制ELISION的根本问题是它仅允许特定列表的特定列表。 (简要介绍,RVO和从Prvalue初始化,允许NRVO,并且许多案例也是如此,具有异常和考文表。没有别的。)有一种哲学原因,这是一个哲学原因:YOYWROTE为您的课程的复制构造函数,可以做任何事情,当您对C ++的规则复制到C ++的对象时,您希望它运行。如果编译器是unprevicticledremove副本,因此删除了复制/移动构造函数和ramp;析构函数调用,他们可能会破坏您的代码。

具体来说,对于复制的ELINION的QuancedFircumstances列表中,适用于我们索引的示例的允许鼠标。该列表不包括“我最后一次使用在其范围内的最后一次使用”或“当我没有使用其他任何别的任何别的时出现的可移量和它的速度和它明显安全”。也许它将在未来,但不是在C ++之前!