[C++ Weekly] EP2 Cost of Using Statics

C++ 11 为了确保 static 初始化线程安全,存在一定开销

#include <string>
#include <algorithm>

struct C {
static const std::string &magic_static()
{
static const std::string s = "bob";
return s;
}
const std::string &s = magic_static();

const std::string &magic_static_ref()
{
return s;
}
};

auto main() -> int
{
/*
C::magic_static().size();
C::magic_static().size();
C::magic_static().size();
return C::magic_static().size();
*/
C c;
c.magic_static_ref().size();
c.magic_static_ref().size();
c.magic_static_ref().size();
return c.magic_static_ref().size();
}
  • 我们创建了一个名为“C”的简单结构,在这个结构中,我们有一个使用“首次使用时构造”习语创建的静态,所以这个静态并不真正存在。 直到第一次调用这个 ‘magic_static’ 函数时才构造字符串’S’。
  • C++ 11 保证了静态变量的初始化以线程安全的方式进行。
  • ​magic_static_ref​​​ 不是​​static​​​ ,所以返回的是成员变量​​s​​的引用而不是对静态变量的引用

编译器视角

用 ​​C++ Insights​​​ 看 ​​struct C​

struct C
{
static inline const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & magic_static()
{
static uint64_t __sGuard;
alignas(const std::basic_string<char, std::char_traits<char>, std::allocator<char> >) static char __s[sizeof(const std::basic_string<char, std::char_traits<char>, std::allocator<char> >)];

if( ! __sGuard )
{
if( __cxa_guard_acquire(&__sGuard) )
{
try
{
new (&__s) std::basic_string<char, std::char_traits<char>, std::allocator<char> >("bob", std::allocator<char>());
__sGuard = true;
}
catch(...)
{
__cxa_guard_abort(&__sGuard);
throw;
}

__cxa_guard_release(&__sGuard);
}
}
return *reinterpret_cast<const std::basic_string<char, std::char_traits<char>, std::allocator<char> >*>(__s);
}

const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & s = magic_static();
inline const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & magic_static_ref()
{
return this->s;
}

};

可以看到为了保证 static 初始化线程安全有一个类似双检查锁的动作,这会带来一定开销

汇编层面

​magic_static​

C::magic_static[abi:cxx11]():           # @C::magic_static[abi:cxx11]()
push rbp
mov rbp, rsp
sub rsp, 32
cmp byte ptr [rip + guard variable for C::magic_static[abi:cxx11]()::s[abi:cxx11]], 0
jne .LBB1_4
lea rdi, [rip + guard variable for C::magic_static[abi:cxx11]()::s[abi:cxx11]]
call __cxa_guard_acquire@PLT
cmp eax, 0
je .LBB1_4
lea rdi, [rbp - 8]
mov qword ptr [rbp - 32], rdi # 8-byte Spill
call std::allocator<char>::allocator()@PLT
mov rdx, qword ptr [rbp - 32] # 8-byte Reload
lea rdi, [rip + C::magic_static[abi:cxx11]()::s[abi:cxx11]]
lea rsi, [rip + .L.str]
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
jmp .LBB1_3

​magic_static_ref​

C::magic_static_ref[abi:cxx11]():       # @C::magic_static_ref[abi:cxx11]()
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov rax, qword ptr [rbp - 8]
mov rax, qword ptr [rax]
pop rbp
ret

Quick C++ Benchmarks

​https://www.quick-bench.com/​

#include <string>
#include <algorithm>
#include <benchmark/benchmark.h>

struct C {
static const std::string &magic_static()
{
static const std::string s = "bob";
return s;
}
const std::string &s = magic_static();

const std::string &magic_static_ref()
{
return s;
}
};

static void BM_MAGIC_STATIC_REF(benchmark::State& state) {
C c;
for (auto _ : state)
for(int i = 0; i < 1'000'000'000; ++i) {
c.magic_static_ref().size();
}
}
// Register the function as a benchmark
BENCHMARK(BM_MAGIC_STATIC_REF);

// Define another benchmark
static void BM_MAGIC_STATIC(benchmark::State& state) {
for (auto _ : state)
for(int i = 0; i < 1'000'000'000; ++i) {
C::magic_static().size();
}
}
// Register the function as a benchmark
BENCHMARK(BM_MAGIC_STATIC);
  • ​O0​​​: ​​magic_static_ref​​​ 慢于 ​​magic_static​
  • ​O1 ~ O3​​​: ​​magic_static_ref​​ 直接被编译器优化掉了

​O0​

[C++ Weekly] EP2 Cost of Using Statics_#include

​O1​

[C++ Weekly] EP2 Cost of Using Statics_开发语言_02

​O2​

[C++ Weekly] EP2 Cost of Using Statics_开发语言_03

​O3​

[C++ Weekly] EP2 Cost of Using Statics_#include_04