[C++ Weekly] EP2 Cost of Using Statics
原创
©著作权归作者所有:来自51CTO博客作者鱼竿钓鱼干的原创作品,请联系作者获取转载授权,否则将追究法律责任
[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
O1
O2
O3