Рассмотрим следующий класс, созданный в основном для целей бенчмаркинга:
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); }
Я пытаюсь сравнить эффективность обмена двух его экземпляров с пользовательскими 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
, которая точно соответствует обмену двумя указателями.Однако для 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
Мне интересно, почему GCC генерирует такой неэффективный код.Инструкция (1) устанавливает [rdi]
в ноль.Этот ноль затем загружается в rax
(2).И затем, rax
проверяется (3), должен ли operator delete
вызываться или нет.
Почему GCC проверяет rax
, если он гарантированно равен нулю?Похоже, оптимизатору достаточно просто избежать этого теста.
Демонстрация Godbolt: https://godbolt.org/z/WNm2if
Другой источник неэффективности заключается в том, что сначала 0 записывается в [rsi]
(A), а затем перезаписывается другим значением (B).
Итог: я ожидаю, что компилятор сгенерирует тот же машинный код для std::swap
, а также для пользовательского swap
, чего не происходит.Это указывает на то, что написание пользовательских функций обмена имеет смысл даже для классов, которые поддерживают семантику перемещения.