Dan*_*ica 7 c++ optimization assembly x86-64
考虑以下类,主要用于基准测试目的:
class String {
char* data_;
public:
String(const char* arg = "") : data_(new char[strlen(arg) + 1]) { strcpy(data_, arg); }
String(const String& other) : String(other.data_) { }
String(String&& other) noexcept : data_(other.data_) { other.data_ = nullptr; }
String& operator=(String other) noexcept { swap(other); return *this; }
~String() { delete[] data_; }
void swap(String& rhs) noexcept { std::swap(data_, rhs.data_); }
const char* data() const { return data_; }
};
void swap(String& lhs, String& rhs) noexcept { lhs.swap(rhs); }
Run Code Online (Sandbox Code Playgroud)
我试图比较两个实例与自定义swap和交换的效率std::swap.对于自定义swap,GCC 8.2(-O2)生成以下x86_64程序集:
mov rax, QWORD PTR [rdi]
mov rdx, QWORD PTR [rsi]
mov QWORD PTR [rdi], rdx
mov QWORD PTR [rsi], rax
ret
Run Code Online (Sandbox Code Playgroud)
它完全匹配两个指针的交换.但是,对于std::swap,生成的程序集是:
mov rdx, QWORD PTR [rsi]
mov QWORD PTR [rsi], 0 // (A)
mov rax, QWORD PTR [rdi]
mov QWORD PTR [rdi], 0 // (1)
mov QWORD PTR [rsi], rax // (B)
mov rax, QWORD PTR [rdi] // (2)
mov QWORD PTR [rdi], rdx
test rax, rax // (3)
je .L3
mov rdi, rax
jmp operator delete[](void*)
.L3:
ret
Run Code Online (Sandbox Code Playgroud)
我很好奇的是为什么GCC会产生如此低效的代码.指令(1)设置[rdi]为零.然后将该零加载到rax(2)中.然后,rax测试(3)是否operator delete应该被调用.
为什么GCC测试rax是否保证为零?对于优化器来说,避免此测试似乎是一个非常简单的情况.
Godbolt演示:https://godbolt.org/z/WNm2if
效率低下的另一个原因是0被写入[rsi]第一个(A)然后被另一个值(B)覆盖.
结论:我希望编译器std::swap为自定义生成相同的机器代码swap,但这不会发生.这表明即使对于支持移动语义的类,编写自定义交换函数也是有意义的.
小智 -1
原因很可能是,虽然第一个代码示例生成了高效的代码,因为程序和所需的可执行文件之间没有抽象(您正在对交换进行编程)。
在第二种情况下,swap 是标准库的成员。可能会将这种检查作为一种安全措施,但 GCC 根本没有优化掉。
基本上,我相信标准库必须有这样的安全检查。为特定任务编写代码将产生针对特定任务优化的代码。您不需要涵盖使用交换可能有意义的所有可能情况。