将 uintptr_t 转换为 bool 会多次减慢 SSO 基准测试

Dan*_*ica 2 c++ performance benchmarking type-conversion microbenchmark

考虑下面的类,它实现了(基本上是为了 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_; }
};
Run Code Online (Sandbox Code Playgroud)

请注意,析构函数有 2 个版本。当我使用 Quick C++ Benchmark 测量这两个版本之间的差异时:

static void CreateShort(benchmark::State& state) {    
   for (auto _ : state) {    
      String s("hello");
      benchmark::DoNotOptimize(s); 
   }  
}
Run Code Online (Sandbox Code Playgroud)

在使用 GCC 的第二种情况下,我的运行时间提高了 5.7 倍。我不明白为什么编译器不能在这里生成相同的优化程序集。如果按位与运算的结果另外转换为 bool,什么会阻碍编译器优化?(虽然我不是汇编器专家,但我可以看到两种变体的汇编输出存在一些差异,但无法弄清楚为什么会有这些差异。)

对于 Clang,没有区别,而且两种变体都很快。


问题在于转换为bool,而不是内联。以下形式的析构函数会导致同样的问题:

~String() { if ((bool)(reinterpret_cast<uintptr_t>(data_) & 1) == false) delete data_; }
Run Code Online (Sandbox Code Playgroud)

M.M*_*M.M 5

对于这段代码:

if (reinterpret_cast<uintptr_t>(data_) & 1 == 0) delete data_;
Run Code Online (Sandbox Code Playgroud)

它可以被完全优化: 1 == 0总是0,并且x & 0对于所有 总是为 false x。第一种情况比较慢,因为它实际上正在做一些事情。

我想你的意思是:

if ( (reinterpret_cast<uintptr_t>(data_) & 1) == 0) delete data_;
Run Code Online (Sandbox Code Playgroud)

我用来表示 的优先级的助记符& |是,在 C 的前身中,没有单独的运算符&&&;运算&符履行了这两个角色(如果您想要逻辑比较,您可以手动转换为布尔范围)。x == y & z == w检查这两个等式是否成立的正常代码也是如此 。

When&&被引入,为了避免破坏现有代码,&&其优先级低于&; 但&保持不变,低于==

C++ 语言也没有改变这些优先级,大概是为了最大限度地减少两种语言之间的不兼容性。