Search code examples
cperformancestaticglobalextern

Will replacing 'extern const' with 'static const' affect performance?


Our C code base stores all global constants across two files:

//global.h
extern const long double ACCELERATION_GRAVITY_FTS;
extern const long double PI;
extern const long double DEG_TO_RAD;
extern const long double RAD_TO_DEG;
extern const long double GC_NM_PER_RAD;
extern const long double FEET_PER_NM;
...

//global.c
const long double ACCELERATION_GRAVITY_FTS = 32.17405;
const long double PI = 3.1415926535897932384626433832795;
const long double DEG_TO_RAD = 0.01745329251994329576923690768489;
const long double RAD_TO_DEG = 57.295779513082320876798154814105;
const long double GC_NM_PER_RAD = 3437.74677471314;
const long double FEET_PER_NM = 6076.1155;

To avoid repetition, I would like to refactor these into a single file:

//global.h
static const long double ACCELERATION_GRAVITY_FTS = 32.17405;
static const long double PI = 3.1415926535897932384626433832795;
static const long double DEG_TO_RAD = 0.01745329251994329576923690768489;
static const long double RAD_TO_DEG = 57.295779513082320876798154814105;
static const long double GC_NM_PER_RAD = 3437.74677471314;
static const long double FEET_PER_NM = 6076.1155;
//global.c no longer exists

Although this is clearly a good refactor in terms of maintainability,
is it practical for performance and executable size?


Solution

  • A static const variable will be a compile-time constant unless volatile or initialized by a function. (with any decent optimizing compiler, anyway)

    So if you do go with static const variables, you could get a speed increase and a smaller binary.

    An example would be:

    extern volatile const int n;
    
    int main(){
        volatile int i = n;
    }
    
    volatile const int n = 5;
    

    Which has the x86 assembly:

    main:
        mov eax, DWORD PTR n[rip]
        mov DWORD PTR [rsp-4], eax
        xor eax, eax
        ret
    n:
        .long   5
    

    we have to use volatile to force the compiler to take the variable's address (that would happen if the variable weren't volatile but were in another translation unit) and not optimize out the int i.

    That same example with static const:

    static const int n = 5;
    
    int main(){
        volatile int i = n;
    }
    

    has the x86 assembly:

    main:
        mov DWORD PTR [rsp-4], 5
        xor eax, eax
        ret
    

    We don't have to use volatile on the constant because we would be exposing the variable exactly the same way as if we were using a header, but we still need to stop the compiler optimizing out i.

    You can see that the static const way has one less instruction and that the extern way has extra data added to the binary for the literal that needs to be stored for reference.

    So we get better performance and a smaller binary. Although, I admit, these examples are pretty trivial.

    This is actually still not a perfect representation, either. If we had const int n defined in another translation unit, without link-time optimizations, the compiler wouldn't be able to output n: .long 5 and would have to reference another variable. But we'll give the example the benefit of the doubt.

    These optimizations are extremely common and you can essentially rely on it being available.

    The only thing to watch out for is if you write something like this:

    static const int n = some_func();
    
    int main(){
        volatile int i = n;
    }
    

    the compiler won't be able to substitute n for its literal value. This would add bloat to your binary because you're defining it in a header and it will be re-declared once in every translation unit. So extern would be better for space in that case, maybe not in speed though; you can test that yourself. Just mix and match if you really need to micro-optimize.

    [all of the examples were compiled with gcc 4.9.2 from https://gcc.godbolt.org/ and used the flag -O3]