我们知道本地静态变量初始化在C++11中是线程安全的,现代编译器完全支持这一点。(本地静态变量初始化线程在C++11中安全吗?)
让它成为线程安全的成本是多少?我知道这很可能是编译器实现依赖的。
上下文:我有一个多线程应用程序(10个线程)通过以下函数以非常高的速率访问单个对象池实例,我担心它的性能影响。
template <class T>
ObjectPool<T>* ObjectPool<T>::GetInst()
{
static ObjectPool<T> instance;
return &instance;
}发布于 2016-07-15 08:34:37
查看生成的汇编程序代码。帮了忙。
来源
#include <vector>
std::vector<int> &get(){
static std::vector<int> v;
return v;
}
int main(){
return get().size();
}汇编程序
std::vector<int, std::allocator<int> >::~vector():
movq (%rdi), %rdi
testq %rdi, %rdi
je .L1
jmp operator delete(void*)
.L1:
rep ret
get():
movzbl guard variable for get()::v(%rip), %eax
testb %al, %al
je .L15
movl get()::v, %eax
ret
.L15:
subq $8, %rsp
movl guard variable for get()::v, %edi
call __cxa_guard_acquire
testl %eax, %eax
je .L6
movl guard variable for get()::v, %edi
movq $0, get()::v(%rip)
movq $0, get()::v+8(%rip)
movq $0, get()::v+16(%rip)
call __cxa_guard_release
movl $__dso_handle, %edx
movl get()::v, %esi
movl std::vector<int, std::allocator<int> >::~vector(), %edi
call __cxa_atexit
.L6:
movl get()::v, %eax
addq $8, %rsp
ret
main:
subq $8, %rsp
call get()
movq 8(%rax), %rdx
subq (%rax), %rdx
addq $8, %rsp
movq %rdx, %rax
sarq $2, %rax
ret与
来源
#include <vector>
static std::vector<int> v;
std::vector<int> &get(){
return v;
}
int main(){
return get().size();
}汇编程序
std::vector<int, std::allocator<int> >::~vector():
movq (%rdi), %rdi
testq %rdi, %rdi
je .L1
jmp operator delete(void*)
.L1:
rep ret
get():
movl v, %eax
ret
main:
movq v+8(%rip), %rax
subq v(%rip), %rax
sarq $2, %rax
ret
movl $__dso_handle, %edx
movl v, %esi
movl std::vector<int, std::allocator<int> >::~vector(), %edi
movq $0, v(%rip)
movq $0, v+8(%rip)
movq $0, v+16(%rip)
jmp __cxa_atexit我不太擅长汇编程序,但我可以看到,在第一个版本中,v有一个锁,而get没有内联,而在第二个版本中,get基本上没有了。
您可以使用各种编译器和优化标志进行四处游玩,但似乎没有编译器能够内联或优化锁,即使程序显然是单线程的。
您可以将static添加到get,这使gcc在保留锁的同时内联get。
要了解这些锁和额外指令对编译器、标志、平台和周围代码的成本,您需要进行适当的基准测试。
我希望锁具有一定的开销,并且比内联代码慢得多,当您实际使用向量时,内联代码就变得无关紧要了,但是如果不进行测量,就永远无法确定。
发布于 2016-07-15 08:40:54
根据我的经验,这与常规互斥(关键部分)一样昂贵。如果代码经常被调用,请考虑使用普通的全局变量。
发布于 2020-08-21 18:14:28
在这里详细解释了杰森·特纳的https://www.youtube.com/watch?v=B3WWsKFePiM。
我放了一个示例代码来演示这个视频。由于线程安全是主要问题,我尝试从多个线程调用该方法以查看其效果。
您可以认为编译器正在为您实现双重检查锁,即使他们可以做任何他们想要确保线程安全的事情。但是他们至少会添加一个分支来区分第一次初始化,除非优化器急切地在全局范围内进行初始化。
#include <iostream>
#include <string>
#include <vector>
#include <thread>
struct Temp
{
// Everytime this method is called, compiler has to check whether `name` is
// constructed or not due to init-at-first-use idiom. This at least would
// involve an atomic load operation and maybe a lock acquisition.
static const std::string& name() {
static const std::string name = "name";
return name;
}
// Following does not create contention. Profiler showed little bit of
// performance improvement.
const std::string& ref_name = name();
const std::string& get_name_ref() const {
return ref_name;
}
};
int main(int, char**)
{
Temp tmp;
constexpr int num_worker = 8;
std::vector<std::thread> threads;
for (int i = 0; i < num_worker; ++i) {
threads.emplace_back([&](){
for (int i = 0; i < 10000000; ++i) {
// name() is almost 5s slower
printf("%zu\n", tmp.get_name_ref().size());
}
});
}
for (int i = 0; i < num_worker; ++i) {
threads[i].join();
}
return 0;
}名称()版本比我的机器上的get_name_ref()慢5s。
$ time ./test > /dev/null另外,我使用编译器资源管理器查看gcc生成的内容。以下证明了双重检查锁模式:注意所获得的原子负载和保护。
name ()
{
bool retval.0;
bool retval.1;
bool D.25443;
struct allocator D.25437;
const struct string & D.29013;
static const struct string name;
_1 = __atomic_load_1 (&_ZGVZL4namevE4name, 2);
retval.0 = _1 == 0;
if (retval.0 != 0) goto <D.29003>; else goto <D.29004>;
<D.29003>:
_2 = __cxa_guard_acquire (&_ZGVZL4namevE4name);
retval.1 = _2 != 0;
if (retval.1 != 0) goto <D.29006>; else goto <D.29007>;
<D.29006>:
D.25443 = 0;
try
{
std::allocator<char>::allocator (&D.25437);
try
{
try
{
std::__cxx11::basic_string<char>::basic_string (&name, "name", &D.25437);
D.25443 = 1;
__cxa_guard_release (&_ZGVZL4namevE4name);
__cxa_atexit (__dt_comp , &name, &__dso_handle);
}
finally
{
std::allocator<char>::~allocator (&D.25437);
}
}
finally
{
D.25437 = {CLOBBER};
}
}
catch
{
if (D.25443 != 0) goto <D.29008>; else goto <D.29009>;
<D.29008>:
goto <D.29010>;
<D.29009>:
__cxa_guard_abort (&_ZGVZL4namevE4name);
<D.29010>:
}
goto <D.29011>;
<D.29007>:
<D.29011>:
goto <D.29012>;
<D.29004>:
<D.29012>:
D.29013 = &name;
return D.29013;
}https://stackoverflow.com/questions/38391014
复制相似问题