local static or not ?

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

local static or not ?

Post by binbinhfr » May 11th, 2020, 8:23 am

Hi,

I wonder something.
In a function that is never called recursively, is it better to put local variables in static to avoid alloc/desalloc and constructor/destructor successive callings ?

ex:

Code: Select all

std::string f()
{
  static std::string s;
  s = ......
  s += .....
  return(s);
}
is the static really useful ? To avoid stack allocation and constructor...
Because I am often using this "trick" and I wonder if it is really a "classic" of static.
Of course, I am thinking of function with far more local variables, eventually constructed from large classes.

User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Re: local static or not ?

Post by cyboryxmen » May 11th, 2020, 2:12 pm

No
Zekilk

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: local static or not ?

Post by Slidy » May 11th, 2020, 5:38 pm

If it's recursive then static might not work the way you want it to in that example as it overwrites changes of the previous call.

That being said, it usually isn't worth doing this kind of thing it but there is some merit to avoiding common dynamic allocations. For objects like std::string (where Small String Optimization doesn't apply) or for std::vector that internally allocate memory it's a bad idea to be constructing them very often, like in a loop or in a function called many times per frame.

Overall I'd recommend just sticking to regular local variables until you actually notice some kind of performance problem, no good will come out of doing premature optimizations.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: local static or not ?

Post by albinopapa » May 12th, 2020, 2:31 am

To add to the other responses, there is a hidden cost to using local statics. Because the standard requires that the object only be initialized the first time it is encountered, the compiler must secretly place code around the constructor of those items to check if it is the first encounter or not. So there is the added code and conditional branching.

Code: Select all

#include <string>
#include <iostream>

std::string GetStackString()
{
    std::string s = "012345678901234567890123456789";

    s = "I have changed the string";
    
    return s;
}

std::string GetStaticString()
{
    static std::string s = "012345678901234567890123456789";

    s = "I have changed the string";

    return s;
}

int main()
{
    std::cout << GetStackString() << '\n';

    return 0;
}
Local variable compiled code:

Code: Select all

        mov     QWORD PTR [rsp+8], rcx
        sub     rsp, 88                             ; 00000058H
        mov     rax, QWORD PTR __security_cookie
        xor     rax, rsp
        mov     QWORD PTR __$ArrayPad$[rsp], rax
        mov     DWORD PTR $T1[rsp], 0
        lea     rdx, OFFSET FLAT:$SG36021
        lea     rcx, QWORD PTR s$[rsp]
        call    std::string(char const * const);
        npad    1
        lea     rdx, OFFSET FLAT:$SG36022
        lea     rcx, QWORD PTR s$[rsp]
        call    std::string& std::string::operator=(char const * const); 
        lea     rdx, QWORD PTR s$[rsp]
        mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
        call    std::string( std::string&& );
        mov     eax, DWORD PTR $T1[rsp]
        or      eax, 1
        mov     DWORD PTR $T1[rsp], eax
        lea     rcx, QWORD PTR s$[rsp]
        call    std::string::~string(void);
        mov     rax, QWORD PTR __$ReturnUdt$[rsp]
        mov     rcx, QWORD PTR __$ArrayPad$[rsp]
        xor     rcx, rsp
        call    __security_check_cookie
        add     rsp, 88                             ; 00000058H
        ret     0
static local variable compiled code

Code: Select all

        mov     QWORD PTR [rsp+8], rcx
        sub     rsp, 56                             ; 00000038H
        mov     DWORD PTR $T1[rsp], 0
        mov     eax, OFFSET FLAT:_Init_thread_epoch
        mov     eax, eax
        mov     ecx, DWORD PTR _tls_index
        mov     rdx, QWORD PTR gs:88
        mov     rcx, QWORD PTR [rdx+rcx*8]
        mov     eax, DWORD PTR [rax+rcx]
        cmp     DWORD PTR TSS0<`template-parameter-2',tStaticString,std::string,void,int, ?? &>, eax
        jle     SHORT $LN2@GetStaticS
        lea     rcx, OFFSET FLAT:TSS0<`template-parameter-2',tStaticString,std::string,void,int, ?? &>
        call    _Init_thread_header
        cmp     DWORD PTR TSS0<`template-parameter-2',tStaticString,std::string,void,int, ?? &>, -1
        jne     SHORT $LN2@GetStaticS
        lea     rdx, OFFSET FLAT:$SG36037
        lea     rcx, OFFSET FLAT:std::string `std::string GetStaticString(void)'::`2'::s
        call    std::string( char const * const);
        lea     rcx, OFFSET FLAT:void `std::string GetStaticString(void)'::`2'::`dynamic atexit destructor for 's''(void) ; `GetStaticString'::`2'::`dynamic atexit destructor for 's''
        call    atexit
        npad    1
        lea     rcx, OFFSET FLAT:TSS0<`template-parameter-2',tStaticString,std::string,void,int, ?? &>
        call    _Init_thread_footer
$LN2@GetStaticS:
        lea     rdx, OFFSET FLAT:$SG36038
        lea     rcx, OFFSET FLAT:std::string `string GetStaticString(void)'::`2'::s
        call    std::string& std::string::operator=(char const * const);
        lea     rdx, OFFSET FLAT:std::string `std::string GetStaticString(void)'::`2'::s
        mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
        call    std::string::string(std::string const &);
        mov     eax, DWORD PTR $T1[rsp]
        or      eax, 1
        mov     DWORD PTR $T1[rsp], eax
        mov     rax, QWORD PTR __$ReturnUdt$[rsp]
        add     rsp, 56                             ; 00000038H
        ret     0

        push    rbp
        sub     rsp, 32                             ; 00000020H
        mov     rbp, rdx
        lea     rcx, OFFSET FLAT:TSS0<`template-parameter-2',tStaticString,std::string,void,int, ?? &>
        call    _Init_thread_abort
        add     rsp, 32                             ; 00000020H
        pop     rbp
        ret     0
I also forgot to mention, but realizing after reading through the assembly, the standard also requires the initialization of static variables to be thread safe if I recall correctly. It looks like this code creates a thread local version of 's' for the static local code. This means that each thread that enters the GetStaticString() function will get their own copy of 's'.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

binbinhfr
Posts: 78
Joined: May 9th, 2019, 10:57 pm

Re: local static or not ?

Post by binbinhfr » May 12th, 2020, 6:48 am

Ok; thanks, so my first idea was not good. It seems that it would be "lighter" to create classic local variables on the stack.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: local static or not ?

Post by albinopapa » May 12th, 2020, 8:54 am

Something you could do is have an #if/#else/#endif block inside the function. For one condition have the local variables declared on stack and the other declared static.

Code: Select all

#define STATIC_LOCALS 0

void func()
{
#if STATIC_LOCALS == 0
    // declare variables on statck
#else
    // declare variables static
#endif
}
This way you can easily test the cases without having to change the code too much.

I will say this though. One of the benefits of declaring locals on stack is the ability to declare them as const. This can also allow the compiler to make some optimizations it wouldn't be able to do for non-const variables. Not that it will, but it can make certain assumptions since the variables would be guaranteed to not change after instantiation.

Also, assigning values to variables upon declaration allows the values to be placed directly into the memory allocated instead of separately. The main use of a constructor's initializer list is this reason. With static locals, you'd only be copying one value to another.

I very rarely if ever find a need for static locals personally. I may use them during a test for something, but when I get the function working I usually make it a member of a class or pass it in as a parameter either by value or by const& depending on the size and type.

I'm sure there are other places to make optimizations that would be more beneficial and effective.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply