问题
Consider the following code:
#include <utility>
#include <string>
int bar() {
std::pair<int, std::string> p {
123, "Hey... no small-string optimization for me please!" };
return p.first;
}
(simplified thanks to @Jarod42 :-) ...)
I expect the function to be implemented as simply:
bar():
mov eax, 123
ret
but instead, the implementation calls operator new()
, constructs an std::string
with my literal, then calls operator delete()
. At least - that's what gcc 9 and clang 9 do (GodBolt). Here's the clang output:
bar(): # @bar()
push rbx
sub rsp, 48
mov dword ptr [rsp + 8], 123
lea rax, [rsp + 32]
mov qword ptr [rsp + 16], rax
mov edi, 51
call operator new(unsigned long)
mov qword ptr [rsp + 16], rax
mov qword ptr [rsp + 32], 50
movups xmm0, xmmword ptr [rip + .L.str]
movups xmmword ptr [rax], xmm0
movups xmm0, xmmword ptr [rip + .L.str+16]
movups xmmword ptr [rax + 16], xmm0
movups xmm0, xmmword ptr [rip + .L.str+32]
movups xmmword ptr [rax + 32], xmm0
mov word ptr [rax + 48], 8549
mov qword ptr [rsp + 24], 50
mov byte ptr [rax + 50], 0
mov ebx, dword ptr [rsp + 8]
mov rdi, rax
call operator delete(void*)
mov eax, ebx
add rsp, 48
pop rbx
ret
.L.str:
.asciz "Hey... no small-string optimization for me please!"
My question is: Clearly, the compiler has full knowledge of everything going on inside bar()
. Why is it not "eliding"/optimizing the string away? More specifically:
- At the basic level there's the code between then
new()
anddelete()
, which AFAICT the compiler knows results in nothing useful. - Secondarily, the
new()
anddelete()
calls themselves. After all, small-string-optimization is allowed by the standard AFAIK, so even though clang/gcc hasn't chosen to use that - it could have; meaning that it's not actually required to callnew()
ordelete()
there.
I'm particularly interested in what part of this is directly due to the language standard, and what part is compiler non-optimality.
回答1:
Nothing in your code represents "elision" as that term is commonly used in a C++ context. The compiler is not permitted to remove anything from that code on the grounds of "elision".
The only grounds a compiler has to remove the creation of that string is on the basis of the "as if" rule. That is, is the behavior of the string creation/destruction visible to the user and therefore not able to be removed?
Since it uses std::allocator
and the standard character traits, the basic_string
construction and destruction itself is not being overridden by the user. So there is some basis for the idea that the string's creation is not a visible side-effect of the function call and thus could be removed under the "as if" rule.
However, because std::allocator::allocate
is specified to call ::operator new
, and operator new
is globally replaceable, it is reasonable to argue that this is a visible side effect of the construction of such a string. And therefore, the compiler cannot remove it under the "as if" rule.
If the compiler knows that you have not replaced operator new
, then it can in theory optimize the string away.
That doesn't mean that any particular compiler will do so.
回答2:
Following the discussion in various answers and comments here, I have now filed the following bugs against GCC and LLVM regarding this issue:
GCC bug 94293: [missed optimization] new+delete of unused local string not removed
Minimal testcase (GodBolt):
void foo() { int *p = new int[1]; *p = 42; delete[] p; }
GCC bug 94294: [missed optimization] Useless statements populating local string not removed
Minimal testcase (GodBolt):
void foo() { std::string s { "This is not a small string" }; }
LLVM bug 45287: [missed optimization] failure to drop unused libstdc++ std::string.
Minimal testcase (GodBolt):
void foo() { std::string s { "This is not a small string" }; }
Thanks goes to: @JeffGarret, @NicolBolas, @Jarod42, Marc Glisse .
回答3:
The question is can the program
int bar() {
std::pair<int, std::string> p {
123, "Hey... no small-string optimization for me please!" };
return p.first;
}
be validly be optimized to
int bar() {
return 123;
}
tldr, yes, I think.
And clang does with libc++: godbolt
About std::string
, the standard says string.require/3
Every object of type basic_string uses an object of type Allocator to allocate and free storage for the contained charT objects as needed.
"as needed". std::string
is allowed to decide when to use the allocator (which is I believe the justification for SSO being valid). Its member functions do not mandate an allocation. Therefore the allocation may be elided.
来源:https://stackoverflow.com/questions/60816079/is-this-elision-failure-language-mandated