即使零值得到保证,GCC也会生成非绝对测试

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 根本没有优化掉。

基本上,我相信标准库必须有这样的安全检查。为特定任务编写代码将产生针对特定任务优化的代码。您不需要涵盖使用交换可能有意义的所有可能情况。