user11594134
user11594134

Reputation: 129

c++ static variable in function performance

I am not quite familiar with static variable in a function in C++. I know it is only initialize once. e.g.

void func(map<int, int>& m){
   static int a = m[0];
}

I would expect static int a = m[0] would only execute once when I first call the function, but it seems to me that every time I call the function, it cost some time to execute m[0]. Here is one test I do. the program itself does not make sense (it will always return the same number) but just want to show the performance as a example

    double getDirect(int i, map<int, double>& m){
    static double res = i;
    return res;
    }

    double getFromMap(int i, map<int, double>& m){
    static double res = m[i];
    return res;
    }

in main function

clock_t t;
t = clock();
long long i = 0;
map<int, double> m;
for(int i = 0; i < 10; i++){
    m.insert(make_pair(i, i));
}
#pragma omp parallel for private(i)
for(i = 0; i < size; i++){
    for(int j = 0; j < 10; j++)
        double res = getDirect(j, m);
}

t = clock()-t;
cout << " It cost " << t << " clicks (" << ((float)t) / CLOCKS_PER_SEC << " sec) to run." << endl;

t = clock();
#pragma omp parallel for private(i)
for(i = 0; i < size; i++){
    for(int j = 0; j < 10; j++)
        double res = getFromMap(j, m);        
}
t = clock()-t;
cout << " It cost " << t << " clicks (" << ((float)t) / CLOCKS_PER_SEC << " sec) to run." << endl;

I would expect the time between the two would be really similar. But the result is

It cost 14055 clicks (14.055 sec) to run. It cost 150636 clicks (150.636 sec) to run.

The getFromMap is much slower. Is this because m[i] is still executed every time? If not, what is the reason? If so, what is a good way to by-pass this performance cost? Thanks.

Here is some follow up. I get the assembly code\

    static double res = m[i];
000000013F83D454  mov         eax,104h  
000000013F83D459  mov         eax,eax  
000000013F83D45B  mov         ecx,dword ptr [_tls_index (013F8503C8h)]  
000000013F83D461  mov         rdx,qword ptr gs:[58h]  
000000013F83D46A  mov         rcx,qword ptr [rdx+rcx*8]  
000000013F83D46E  mov         eax,dword ptr [rax+rcx]  
000000013F83D471  cmp         dword ptr [res+0Ch (013F85035Ch)],eax  
000000013F83D477  jle         getFromMap+0A9h (013F83D4B9h)  
000000013F83D479  lea         rcx,[res+0Ch (013F85035Ch)]  
000000013F83D480  call        _Init_thread_header (013F831640h)  
000000013F83D485  cmp         dword ptr [res+0Ch (013F85035Ch)],0FFFFFFFFh  
000000013F83D48C  jne         getFromMap+0A9h (013F83D4B9h)  
000000013F83D48E  lea         rdx,[i]  
000000013F83D495  mov         rcx,qword ptr [m]  
000000013F83D49C  call        std::map<int,double,std::less<int>,std::allocator<std::pair<int const ,double> > >::operator[] (013F831320h)  
000000013F83D4A1  movsd       xmm0,mmword ptr [rax]  
000000013F83D4A5  movsd       mmword ptr [res (013F850360h)],xmm0  
000000013F83D4AD  lea         rcx,[res+0Ch (013F85035Ch)]  
000000013F83D4B4  call        _Init_thread_footer (013F8314C4h)

It did seem that m[i] is still called after the first time. I also did a single thread test with a smaller size, but the difference is even larger (a ratio about 100).

Any idea on how to set up visual studio 2012 so that it stops calling m[i] after the first time? Thanks so much for your help

Upvotes: 3

Views: 1706

Answers (2)

Jarod42
Jarod42

Reputation: 217135

From quick-bench

It seems that getDirect is inlined contrary to getFromMap.

Probably due to size of code to call m[i] in the initialization case.

(version with at is even faster that your getDirect Demo :) )

Upvotes: 2

SergeyA
SergeyA

Reputation: 62563

Having function-local static variables would ever so slightly penalize you. Since static values need to be initialized once, and only once, there has to be a flag which would be checked on every function execution, set to 'yes, please initialize' before the function is entered first time and reset to 'no, please no longer initialize' after such initialization has happened.

Otherwise, compiler would have no way to guarantee you a single initialization.

However, compiler guarantees you that once the variable has been initialized, no further initialization will take place, and the declaration will be skipped: https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

We can look at a sample codegen to confirm this assumption with the compiler: https://gcc.godbolt.org/z/Q-Dni_ It is clear that if the variable is already initialized, a declaration is skipped and k is not called. Conclusion: your difference in time comes from somewhere else.

Upvotes: 7

Related Questions