Dev*_*ull 25 c++ static global-variables
静态局部变量在第一个函数调用时初始化:
在块作用域中使用指定符static声明的变量具有静态存储持续时间,但在控件第一次通过其声明时初始化(除非它们的初始化为零或常量初始化,这可以在首次输入块之前执行).在所有进一步的调用中,将跳过声明.
此外,在C++ 11中还有更多检查:
如果多个线程同时尝试初始化相同的静态局部变量,则初始化只发生一次(使用std :: call_once可以获得类似的任意函数行为).注意:此功能的常规实现使用双重检查锁定模式的变体,这可以将已初始化的局部静态的运行时开销减少到单个非原子布尔比较.(自C++ 11以来)
同时,全局变量似乎在程序启动时初始化(尽管技术上只在cppreference上提到了分配/解除分配):
静态存储时间.程序开始时分配对象的存储空间,程序结束时分配存储空间.只存在一个对象实例.在命名空间范围(包括全局命名空间)声明的所有对象都具有此存储持续时间,以及使用static或extern声明的持续时间
所以给出以下示例:
struct A {
// complex type...
};
const A& f()
{
static A local{};
return local;
}
A global{};
const A& g()
{
return global;
}
Run Code Online (Sandbox Code Playgroud)
我是否正确地假设f()
每次调用它时必须检查其变量是否已初始化,因此f()
会慢于g()
?
Bat*_*eba 15
当然,你在概念上是正确的,但现代架构可以解决这个问题.
现代编译器和体系结构将安排管道,以便假定已经初始化的分支.因此,初始化的开销会产生额外的管道转储,这就是全部.
如果您有任何疑问,请检查组件.
是的,它几乎肯定会稍微慢一些.然而,大部分时间它都无关紧要,成本将超过"逻辑和风格"的好处.
从技术上讲,函数本地静态变量与全局变量相同.只是它的名称不是全局已知的(这是一件好事),并且它的初始化保证不仅发生在确切的指定时间,而且只发生一次,并且线程安全.
这意味着函数本地静态变量必须知道初始化是否已经发生,因此需要至少一个额外的内存访问和一个全局(原则上)不需要的条件跳转.实现可能会对全局变量做类似的事情,但它不需要(通常也不需要).
在所有情况下都可以正确预测跳跃,但有两个跳跃是很好的.前两个调用很可能被预测为错误(通常默认假设是跳跃,而不是第一次调用时的错误假设,并且假设后续跳转采用与最后一个相同的路径,同样错误).在那之后,你应该好好去,接近100%正确的预测.
但即使是正确预测的跳转也不是免费的(CPU仍然只能在每个周期启动给定数量的指令,即使假设它们没有时间完成),但它并不多.如果可以成功隐藏在最坏情况下可能是几百个周期的存储器延迟,则流水线中的成本几乎消失.此外,每次访问都会获取一个额外的高速缓存行,否则不需要该高速缓存行(已经初始化的标志可能不会存储在与数据相同的高速缓存行中).因此,你的L1性能稍差(L2应该足够大,所以你可以说"是的,那么什么").
它还需要实际执行一次和线程安全的全局(原则上)不必做,至少不是以你看到的方式.一个实现可以做一些不同的事情,但大多数只是在main
输入之前初始化全局变量,并且很少大部分是用a memset
或隐式完成的,因为变量存储在一个归零的段中.执行初始化代码时,必须初始化
静态变量,并且必须以线程安全的方式进行.根据您的实施情况糟透了,这可能非常昂贵.我决定放弃线程安全功能,并且在发现GCC(否则是一个OK allround编译器)实际上会为每个静态初始化锁定一个互斥锁后,总是编译(即使这不符合标准).fno-threadsafe-statics