Is access to a static function variable slower than access to a global variable?

后端 未结 4 1931
慢半拍i
慢半拍i 2021-02-05 04:21

Static local variables are initialised on the first function call:

Variables declared at block scope with the specifier static have static storage duratio

相关标签:
4条回答
  • 2021-02-05 05:02

    g() is not thread-safe, and is susceptible to all sorts of ordering problems. Safety is going to come at a price. There are several ways to pay it:

    f(), the Meyer's Singleton, pays the price on every access. If accessed frequently or accessed during a performance-sensitive section of your code, then it does make sense to avoid f(). Your processor presumably has a finite number of circuits it can devote to branch prediction, and you are being forced to read an atomic variable before the branch anyway. It is a tall price to continually pay for just ensuring that the initialization happened only once.

    h(), described below, works very much like g() with an extra indirection, but assumes that h_init() gets called exactly once at the beginning of execution. Preferably, you would define a subroutine that gets called as the line of main(); that calls every function like h_init(), with an absolute ordering. Hopefully, these objects do not need to be destructed.

    Alternatively, if you use GCC, you can annotate h_init() with __attribute__((constructor)). I prefer the explicitness of the static init subroutine though.

    A * h_global = nullptr;
    void h_init() { h_global = new A { }; }
    A const& h() { return *h_global; }
    

    h2() is just like h(), minus the extra indirection:

    alignas(alignof(A)) char h2_global [sizeof(A)] = { };
    void h2_init() { new (std::begin(h2_global)) A { }; }
    A const& h2() { return * reinterpret_cast <A const *> (std::cbegin(h2_global)); }
    
    0 讨论(0)
  • 2021-02-05 05:06

    You are conceptually correct of course, but contemporary architectures can deal with this.

    A modern compiler and architecture would arrange the pipeline such that the already-initialised branch was assumed. The overhead of initialisation would therefore incur an extra pipeline dump, that's all.

    If you're in any doubt, check the assembly.

    0 讨论(0)
  • 2021-02-05 05:06

    Yes, it is almost certainly slightly slower. Most of the time it will however not matter and the cost will be outweighted by the "logic and style" benefit.

    Technically, a function-local static variable is the same as a global variable. Only just that its name is not globally known (which is a good thing), and its initialization is guaranteed to happen not only at an exactly specified time, but also only once, and threadsafe.

    This means that a function-local static variable must know whether initialization has happened, and thus needs at least one extra memory access and one conditional jump that the global (in principle) doesn't need. An implemenation may do someting similar for globals, but it needs not (and usually doesn't).

    Chances are good that the jump is predicted correctly in all cases but two. The first two calls are highly likely to be predicted wrong (usually jumps are by default assumed to be taken rather than not, wrong assumption on first call, and subsequent jumps are assumed to take the same path as the last one, again wrong). After that, you should be good to go, near 100% correct prediction.
    But even a correctly predicted jump isn't free (the CPU can still only start a given number of instructions every cycle, even assuming they take zero time to complete), but it's not much. If the memory latency, which may be a couple of hundred cycles in the worst case can be successfully hidden, the cost almost disappears in pipelining. Also, every access fetches an extra cacheline that wouldn't otherwise be needed (the has-been-initialized flag likely isn't stored in the same cache line as the data). Thus, you have slightly worse L1 performance (L2 should be big enough so you can say "yeah, so what").

    It also needs to actually perform something once and threadsafe that the global (in principle) doesn't have to do, at least not in a way that you see. An implementation can do something different, but most just initialize globals before main is entered, and not rarely most of it is done with a memset or implicitly because the variable is stored in a segment that is zeroed anyway.
    Your static variable must be initialized when the initialization code is executed, and it must happen in a threadsafe manner. Depending on how much your implementation sucks this can be quite expensive. I decided to forfeit on the thread safety feature and always compile with fno-threadsafe-statics (even if this isn't standard-compliant) after discovering that GCC (which is otherwise an OK allround compiler) would actually lock a mutex for every static initialization.

    0 讨论(0)
  • 2021-02-05 05:06

    From https://en.cppreference.com/w/cpp/language/initialization

    Deferred dynamic initialization
    It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.

    If the initialization of a non-inline variable (since C++17) is deferred to happen after the first statement of main/thread function, it happens before the first odr-use of any variable with static/thread storage duration defined in the same translation unit as the variable to be initialized.

    So similar check may have to be done also for global variables.

    so f() is not necessary "slower" than g().

    0 讨论(0)
提交回复
热议问题