Рассмотрим следующий класс, который реализует (очень в основном для MCVE) оптимизация небольших строк (при условии использования байтов с прямым порядком байтов, 64-битных указателей и т. Д.):
class String {
char* data_;
bool sso() const { return reinterpret_cast<uintptr_t>(data_) & 1; }
public:
String(const char * arg = "") {
auto len = strlen(arg);
if (len > 6) {
data_ = new char[len + 1];
memcpy(data_, arg, len + 1);
}
else {
data_ = reinterpret_cast<char*>((uintptr_t)1);
memcpy(reinterpret_cast<char*>(&data_) + 1, arg, len + 1);
}
}
~String() { if (sso() == false) delete data_; }
// ~String() { if (reinterpret_cast<uintptr_t>(data_) & 1 == 0) delete data_; }
};
Обратите внимание, что существует 2 версии деструктора. Когда я измерил разницу между этими двумя версиями с помощью Quick C ++ Benchmark:
static void CreateShort(benchmark::State& state) {
for (auto _ : state) {
String s("hello");
benchmark::DoNotOptimize(s);
}
}
Я получил в 5,7 раза быстрее время работы во втором случае с GCC. Я не понимаю, почему компилятор не может генерировать такую же оптимизированную сборку здесь. Что мешает оптимизации компилятора в случае, если результат побитовой операции И дополнительно конвертируется в bool? (Хотя я не эксперт по ассемблеру, я вижу некоторые различия в выходах сборки для обоих вариантов, но не могу понять, почему они существуют.)
С Clang разницы нет, и оба варианта быстрые.
Проблема с преобразованием в bool
, а не с встраиванием. Деструктор следующей формы вызывает ту же проблему:
~String() { if ((bool)(reinterpret_cast<uintptr_t>(data_) & 1) == false) delete data_; }