将 uintptr_t 转换为 bool 会多次减慢 SSO 基准测试
Conversion of uintptr_t into bool slows down SSO benchmark several times
考虑以下 class 实现(基本上是为了 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);
}
}
在使用 GCC 的第二种情况下,我的速度 5.7 倍 运行。我不明白为什么编译器不能在这里生成相同的优化程序集。如果按位 AND 运算的结果额外转换为 bool,是什么阻碍了编译器优化? (虽然我不是汇编专家,但我可以看到两种变体的汇编输出存在一些差异,但无法弄清楚为什么会存在差异。)
使用 Clang 没有区别,两种变体都很快。
问题在于转换为 bool
,而不在于内联。以下形式的析构函数导致同样的问题:
~String() { if ((bool)(reinterpret_cast<uintptr_t>(data_) & 1) == false) delete data_; }
对于此代码:
if (reinterpret_cast<uintptr_t>(data_) & 1 == 0) delete data_;
它可以完全优化掉:1 == 0
总是 0
,并且 x & 0
对于所有 x
总是假的。第一种情况比较慢,因为它实际上在做一些事情。
我想你的意思是:
if ( (reinterpret_cast<uintptr_t>(data_) & 1) == 0) delete data_;
我用于 &
|
优先级的助记符是回忆在 C 的前身中,没有单独的运算符 &
和 &&
; &
运算符满足这两个角色(如果您想要进行逻辑比较,您可以手动转换为布尔值范围)。所以 x == y & z == w
是检查这两个等式是否成立的正常代码。
引入&&
时,为了避免破坏现有代码,&&
的优先级低于&
;但 &
保持不变,低于 ==
。
C++ 语言也没有改变这些优先级,大概是为了尽量减少两种语言之间的不兼容性。
考虑以下 class 实现(基本上是为了 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);
}
}
在使用 GCC 的第二种情况下,我的速度 5.7 倍 运行。我不明白为什么编译器不能在这里生成相同的优化程序集。如果按位 AND 运算的结果额外转换为 bool,是什么阻碍了编译器优化? (虽然我不是汇编专家,但我可以看到两种变体的汇编输出存在一些差异,但无法弄清楚为什么会存在差异。)
使用 Clang 没有区别,两种变体都很快。
问题在于转换为 bool
,而不在于内联。以下形式的析构函数导致同样的问题:
~String() { if ((bool)(reinterpret_cast<uintptr_t>(data_) & 1) == false) delete data_; }
对于此代码:
if (reinterpret_cast<uintptr_t>(data_) & 1 == 0) delete data_;
它可以完全优化掉:1 == 0
总是 0
,并且 x & 0
对于所有 x
总是假的。第一种情况比较慢,因为它实际上在做一些事情。
我想你的意思是:
if ( (reinterpret_cast<uintptr_t>(data_) & 1) == 0) delete data_;
我用于 &
|
优先级的助记符是回忆在 C 的前身中,没有单独的运算符 &
和 &&
; &
运算符满足这两个角色(如果您想要进行逻辑比较,您可以手动转换为布尔值范围)。所以 x == y & z == w
是检查这两个等式是否成立的正常代码。
引入&&
时,为了避免破坏现有代码,&&
的优先级低于&
;但 &
保持不变,低于 ==
。
C++ 语言也没有改变这些优先级,大概是为了尽量减少两种语言之间的不兼容性。