Fer*_*eak 69 c++ gcc compilation clang compiler-optimization
我很好奇为什么下面的代码:
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNO";
}
Run Code Online (Sandbox Code Playgroud)
当使用编译时,将-O3产生以下代码:
main: # @main
xor eax, eax
ret
Run Code Online (Sandbox Code Playgroud)
(我完全理解不需要多余的,a因此编译器可以从生成的代码中完全忽略它)
但是以下程序:
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNOP"; // <-- !!! One Extra P
}
Run Code Online (Sandbox Code Playgroud)
产量:
main: # @main
push rbx
sub rsp, 48
lea rbx, [rsp + 32]
mov qword ptr [rsp + 16], rbx
mov qword ptr [rsp + 8], 16
lea rdi, [rsp + 16]
lea rsi, [rsp + 8]
xor edx, edx
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
mov qword ptr [rsp + 16], rax
mov rcx, qword ptr [rsp + 8]
mov qword ptr [rsp + 32], rcx
movups xmm0, xmmword ptr [rip + .L.str]
movups xmmword ptr [rax], xmm0
mov qword ptr [rsp + 24], rcx
mov rax, qword ptr [rsp + 16]
mov byte ptr [rax + rcx], 0
mov rdi, qword ptr [rsp + 16]
cmp rdi, rbx
je .LBB0_3
call operator delete(void*)
.LBB0_3:
xor eax, eax
add rsp, 48
pop rbx
ret
mov rdi, rax
call _Unwind_Resume
.L.str:
.asciz "ABCDEFGHIJKLMNOP"
Run Code Online (Sandbox Code Playgroud)
用相同的方式编译时-O3。我不明白为什么a不管字符串长一字节,它仍不能识别出仍未使用。
这个问题与gcc 9.1和clang 8.0有关(在线:https : //gcc.godbolt.org/z/p1Z8Ns),因为在我看来,其他编译器要么完全删除未使用的变量(ellcc),要么为其生成代码,无论字符串的长度。
lub*_*bgr 65
这是由于小的字符串优化。当字符串数据少于或等于16个字符(包括空终止符)时,它将存储在std::string对象本身本地的缓冲区中。否则,它将在堆上分配内存并将数据存储在堆上。
第一个字符串"ABCDEFGHIJKLMNO"加上空终止符的大小恰好为16。加法"P"使其超过缓冲区,因此new在内部被调用,不可避免地导致系统调用。如果可以确保没有副作用,则编译器可以优化一些东西。进行系统调用可能无法做到这一点-相比之下,更改正在构造的对象本地的缓冲区可以进行这种副作用分析。
在libstdc ++版本9.1中跟踪本地缓冲区,揭示了以下内容bits/basic_string.h:
Run Code Online (Sandbox Code Playgroud)template<typename _CharT, typename _Traits, typename _Alloc> class basic_string { // ... enum { _S_local_capacity = 15 / sizeof(_CharT) }; union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; // ... };
这样您就可以发现本地缓冲区的大小_S_local_capacity和本地缓冲区本身(_M_local_buf)。当构造函数触发basic_string::_M_construct被调用时,您拥有bits/basic_string.tcc:
Run Code Online (Sandbox Code Playgroud)void _M_construct(_InIterator __beg, _InIterator __end, ...) { size_type __len = 0; size_type __capacity = size_type(_S_local_capacity); while (__beg != __end && __len < __capacity) { _M_data()[__len++] = *__beg; ++__beg; }
本地缓冲区中填充了其内容的位置。在这部分之后,我们到达耗尽本地容量的分支-分配了新的存储(通过中的allocate M_create),将本地缓冲区复制到新的存储中,并填充其余的初始化参数:
Run Code Online (Sandbox Code Playgroud)while (__beg != __end) { if (__len == __capacity) { // Allocate more space. __capacity = __len + 1; pointer __another = _M_create(__capacity, __len); this->_S_copy(__another, _M_data(), __len); _M_dispose(); _M_data(__another); _M_capacity(__capacity); } _M_data()[__len++] = *__beg; ++__beg; }
附带说明,小字符串优化本身就是一个话题。要了解调整单个位如何在很大程度上产生影响,我建议您进行本次演讲。它还提到(libstdc ++)std::string附带的实现是如何gcc工作的,并在过去进行了更改以匹配该标准的较新版本。
Pas*_* By 19
我很惊讶编译器看到了一个std::string构造函数/析构函数对,直到看到第二个示例为止。没有。您在此处看到的是小字符串优化以及编译器对此进行的相应优化。
小字符串优化是指std::string对象本身足够大以容纳字符串的内容,大小以及可能有区别的位,用于指示字符串是在小字符串模式还是大字符串模式下运行。在这种情况下,不会发生动态分配,并且字符串将存储在std::string对象本身中。
编译器在清除不必要的分配和释放方面确实很糟糕,它们几乎被视为具有副作用,因此无法排除。当您超过小的字符串优化阈值时,就会发生动态分配,结果就是您所看到的。
举个例子
void foo() {
delete new int;
}
Run Code Online (Sandbox Code Playgroud)
是可能的最简单,最愚蠢的分配/取消分配对,但即使在O3下,gcc也会发出此程序集
sub rsp, 8
mov edi, 4
call operator new(unsigned long)
mov esi, 4
add rsp, 8
mov rdi, rax
jmp operator delete(void*, unsigned long)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2894 次 |
| 最近记录: |