Are Global variables better for Speed of processing ?

Dear all

I have several functions in my program and each of them has several variables. Although declaring variables of each function locally helps to tidy up my program, I think each time I call a function it repeats the deceleration of local variables; thus, this will reduce the speed. But if I declare all variables globally on top, they are just being declared once, not at each function call. Am I right ?

Thanks

Declaration is not deceleration.
The addressing mode used by locals versus globals may affect the speed, but the benefits of keeping things local far outweighs any possible slight speed issues.

I think the performance depends on whether the local variables are placed into registers or whether they need memory allocated for them. That probably depends on how many locals there are and whether the registers are already in use - which they could be if a function calls a function that calls a function.

I must say I am a programming philistine when it comes to local or global variables on an Arduino. I generally use global variables. In the small size of an Arduino, and especially as I am the only person working on the code, I see little benefit in rigorously using local variables. And C++ does not make it easy to return the value of a local variable.

...R

TheMemberFormerlyKnownAsAWOL: Declaration is not deceleration. The addressing mode used by locals versus globals may affect the speed, but the benefits of keeping things local far outweighs any possible slight speed issues.

I certainly agree with the above in principle and practice.

Performance benefits are certainly negligible for most applications. Code readability, extensibility, and maintainability are arguably negligible for small programs. However, if one wants to learn good programming practices and proceed to more advanced and complex applications, scope of variables should be constrained to the lowest possible level. I would encourage anyone to stick to that practice because it is not only good practice but leads to understanding how the compiler treats variables within scope.

Robin2: And C++ does not make it easy to return the value of a local variable.

I don't understand that statment.

uint8_t function() {
  uint8_t localVariable;

  localVariable = 100;
  return localVariable;
}

How hard is that?

You do have to be careful about returning a POINTER to a local variable. Make sure it's local static.

talking performance-wise I doubt that the declaration in a function will be of a big difference for a typical Arduino usecase.

I tried:

// https://forum.arduino.cc/index.php?topic=661449

int myGlobal;
uint32_t start, end;

int randomGlobal()
{
  myGlobal = random(100, 1000);
  return myGlobal;
}

int randomLocal()
{
  int myLocal;
  myLocal = random(100, 1000);
  return myLocal;
}

int randomStatic()
{
  static int myStatic;
  myStatic = random(100, 1000);
  return myStatic;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println(F("... if you don't believe you have to try on your own"));

  for (byte i = 0; i < 10; i++)
  {
    int output = 0;
    start = micros();
    output = randomGlobal();
    end = micros();
    Serial.print(output);
    Serial.print(" - Global in ");
    Serial.println(end - start);

    start = micros();
    output = randomLocal();
    end = micros();
    Serial.print(output);
    Serial.print(" - Local  in ");
    Serial.println(end - start);

    start = micros();
    output = randomStatic();
    end = micros();
    Serial.print(output);
    Serial.print(" - Static in ");
    Serial.println(end - start);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

gives me on an UNO between 52 and 60 micros:

... if you don't believe you have to try on your own
707 - Global in 52
449 - Local  in 52
573 - Static in 56
858 - Global in 56
230 - Local  in 56
972 - Static in 56
844 - Global in 56
378 - Local  in 56
423 - Static in 56
209 - Global in 56
640 - Local  in 56
765 - Static in 56
592 - Global in 56
342 - Local  in 56
487 - Static in 56
903 - Global in 52
827 - Local  in 56
629 - Static in 52
440 - Global in 60
812 - Local  in 56
403 - Static in 56
569 - Global in 60
109 - Local  in 56
957 - Static in 56
860 - Global in 60
133 - Local  in 56
799 - Static in 56
478 - Global in 56
216 - Local  in 56
935 - Static in 56

If my test is not completely wrong, I will stick to local scopes if global is not necessary.

noiasca: talking performance-wise I doubt that the declaration in a function will be of a big difference for a typical Arduino usecase.

If my test is not completely wrong, I will stick to local scopes if global is not necessary.

Indeed. Also, most compilers are very good at optimizing code so it is possible the compiled output is not that much different for the above cases.

When I started writing code for memory-constrained microprocessors in the mid-80s we did not even consider compiled code. We HAD to program in assembly. We were working with processors that had 64K or less total memory. Some was RAM and some was ROM.

gfvalvo: How hard is that?

It was real easy at this point

You do have to be careful about returning a POINTER to a local variable. Make sure it's local static.

but now it has got real complicated.

...R

@Robin2, I don't get your point?

ToddL1962:
When I started writing code for memory-constrained microprocessors in the mid-80s we did not even consider compiled code. We HAD to program in assembly. We were working with processors that had 64K or less total memory. Some was RAM and some was ROM.

I was programming slow 8080s in the early 1980s in compiled PL/M-80, for real-time telecoms test gear.
Much UV-EPROM (well, more than 12kB) but only a couple of k of RAM.
Not that much of a hardship

Robin2: It was real easy at this point but now it has got real complicated.

This begs the question: why do you want to return pointers to local variables in the first place?

That's certainly not a reason to just make everything global, IMHO.

I've recently worked on a large project where my predecessor held the belief that using global variables for everything would somehow make his code significantly faster. It was the biggest pile of spaghetti I had ever seen, and impossible to maintain. After discovering multiple bugs due to a global variable being modified by functions that were not expected to modify them, we decided to refactor the most important parts of the code base, using local variables and passing everything by function arguments. In the end, the result was 100× easier to reason about and to maintain, and we even managed to improve the performance by almost 10%. (Not directly because of the local variables of course, but because the code was easier to understand and therefore easier to optimize.)

Moral of the story: Make everything local if possible, the performance difference is negligible, and the benefits in readability and maintainability are huge.

There are valid reasons to use globals of course, but performance is rarely one of them.

Pieter

TheMemberFormerlyKnownAsAWOL: I was programming slow 8080s in the early 1980s in compiled PL/M-80, for real-time telecoms test gear. Much UV-EPROM (well, more than 12kB) but only a couple of k of RAM. Not that much of a hardship

It wasn't hard. It just didn't work for us.

Ours was a real-time closed-loop control system for nuclear and fossil-fuel power plants and substations. We also bit-banged custom asynchronous protocols and created our own math library in order to implement a PID algorithm. Everything had to be safety certified. Wouldn't want to make a mistake a blow a steam generator in a nuclear plant.

gfvalvo: @Robin2, I don't get your point?

If it is as easy as the first line I quoted why was it necessary for you to write the second line?

Does this work

int testValue;

void loop() {

  testValue = myFunction():

  delay(500);
}

int myFunction();
   int localValue = 12345;
   return localValue;
}

And what happens if testValue and localValue are arrays?

...R

Robin2:
Does this work

int testValue;

void loop() {

testValue = myFunction():

delay(500);
}

int myFunction();
  int localValue = 12345;
  return localValue;
}

Apart from the syntax errors, yes.

Robin2:
And what happens if testValue and localValue are arrays?

If you’re a C programmer, you use an output parameter, and if you’re a C++ programmer, you use std::array instead of C-style arrays.

#include <ArduinoSTL.h>
#include <array>
using std::array;

array<int, 5> myFunction() {
   array<int, 5> localArray = {1, 2, 3, 4, 5};
   return localArray;
}

array<int, 5> testArray;

void loop() {
  testArray = myFunction();
}

Robin2:
If it is as easy as the first line I quoted why was it necessary for you to write the second line?

Completeness.

Does this work

int testValue;

void loop() {

testValue = myFunction():

delay(500);
}

int myFunction();
  int localValue = 12345;
  return localValue;
}

Of course it does.

And what happens if testValue and localValue are arrays?

Arrays are passed and returned by reference (aka pointer). It’s been that way sense K met R. So, the caveat about returning pointers to local variables applies.

I still don’t get your point. It’s programming, not philosophy or basket weaving. To do it successfully you have to know the language and know what you’re doing.

why do you want to return pointers to local variables in the first place?

The problem usually shows up with "strings."

char *printIP(uint32_t ipAddress) {
    char buffer[20];  // room for printed IP address like 192.168.222.112
    sprintf(buffer, "%d.%d.%d.%d", ipAddress>>24, (ipAddress>>16) & 255,
        (ipAddress>>8) & 255, ipAddress & 255);
    return(buffer);
}

Will not work right, for example. Other arrays and more complex data types have similar problems, but it's the char arrays that bite people most often.

Note that in general, declaring a local variable in C is NOT an "expensive" operation. In C++, if the variable is a complex object type, it might be more expensive to allocation (depending on its constructor)...

PieterP: and if you're a C++ programmer, you use std::array instead of C-style arrays.

I assume these are actually class objects that wrap the array, right? So, are they passed to / from functions on the stack. Seems like that would be an excessive amount of pushing and popping for a large array. If that's the case, I'd just pass the array pointer INTO the function, along with its size.

A hard-core C++ guy would probably consider that too Old School :)

gfvalvo:
I assume these are actually class objects that wrap the array, right?

Correct.

gfvalvo:
So, are they passed to / from functions on the stack. Seems like that would be an excessive amount of pushing and popping for a large array.

Large return values are not really passed on the stack. They are allocated in the calling function, and a pointer to it is then passed to the callee. This is just as cheap as using a pointer as an output parameter in C. An std::array as return value is not copied.

From the AVR calling convention:

Return values with a size of 1 byte up to and including a size of 8 bytes will be returned in registers. Return values whose size is outside that range will be returned in memory.
If a return value cannot be returned in registers, the caller will allocate stack space and pass the address as implicit first pointer argument to the callee. The callee will put the return value into the space provided by the caller.

Objects larger than 8 bytes are always returned “by reference”. This is true for objects of any type, including std::array.

This means that for arrays that are larger than 8 bytes, there is no difference between passing an output parameter or returning an std::array. For small arrays, returning the array will probably even be faster than using an output parameter, because everything happens in registers.

For example: https://godbolt.org/z/M8L3oo:

array<int, 500> bar() {
    return {1, 2, 3};
}

void foo() {
    array<int, 500> array = bar();
}
; note how the bar function doesn't allocate any memory on the stack for the return value
; it just writes the numbers 1, 2, 3 to the array allocated in the foo function
; no copying of arrays takes place
bar():
        push    {r4, lr}      ; save r4 and the return address
        mov     r4, r0        ; r4 and r0 contain a pointer to the array allocated in foo
        mov     r2, #2000     ; load the size parameter for memset
        mov     r1, #0        ; load the value parameter for memset
        bl      memset        ; call memset(array, 0, 2000) to zero-initialize the array
        mov     r3, #1        ; load value "1"
        str     r3, [r4]      ; store it in 0th element of the array pointed to by r4
        mov     r3, #2        ; load the value "2"
        str     r3, [r4, #4]  ; store it in the 1st element of the array pointed to by r4
        mov     r3, #3        ; load the value "3"
        str     r3, [r4, #8]  ; store it in the 2nd element of the array pointed to by r4
        mov     r0, r4        ; save the pointer to the array in r0 again
        pop     {r4, lr}      ; restore r4 and the return address
        bx      lr            ; return

foo():
        str     lr, [sp, #-4]! ; save the return address (link register) on the stack
        sub     sp, sp, #2000  ; allocate 2000 bytes (500 integers) on the stack
        sub     sp, sp, #4
        mov     r0, sp         ; store a pointer to the array in r0 (first implicit argument)
        bl      bar()          ; call bar()
        add     sp, sp, #2000  ; deallocate the array
        add     sp, sp, #4
        ldr     lr, [sp], #4   ; restore the return address
        bx      lr             ; return

To pass large arrays as parameters, it’s best to pass them by (const) reference to prevent copies.

gfvalvo: It's programming, not philosophy or basket weaving. To do it successfully you have to know the language and know what you're doing.

Yes of course. Using global variables you can get away with knowing less :)

How can a local array be "returned" if the memory for the array gets wiped out when the function completes?

These things are much easier in Python (or at least seem to be).

...R

Robin2: How can a local array be "returned" if the memory for the array gets wiped out when the function completes?

@PieterP pointed out two possible ways.

  • You can create the array in the calling function and pass a pointer to the callee function.

  • You can wrap the array in a class (or struct) and pass it by value (a notional "copy") just like any other argument is passed in C / C++ (I'm ignoring C++ references for this discussion).

As an old-time C guy (learned in college 30+ years ago), I lean towards the former. But, @PieterP notes that the latter method doesn't actually make copies and push / pop the stack with the array. The compiler is much cleverer than that.

So, in the end, both methods are actually working with pointers.