Help with local and global variable

Hello all arduino lovers!
A lot of time I can’t solve one problem with Arduino UNO.

The execution time of the espera2 () function is 0-1 mS, if the variables S, T, U, V, W and divisor were global the execution time is 390mS as in the espera1() function .

The only difference between the functions is that one uses local variables and the other uses global variables .

I thought I understood the use of local and global variables. Can someone explain to me why this happens?

The program works perfectly if instead of local variables I use global variables.

Thank you for your help.

//---------------------------------------------- PROGRAM --------------------------
unsigned long int tiempo;

unsigned int S,T,U,V,W;  // global variables
unsigned int divisor;


//---------------------------------------------------------------------------------------------------------------------
void espera2(void)
{
  unsigned int S,T,U,V,W;  // local variables
  unsigned int divisor=0;

  for (S=0;S<1;S=S+1)

{
       for (T=0;T<1;T=T+1)
         {
          
          for (U=0;U<900;U=U+1)
            {
              for (V=0;V<1000;V=V+1)
              {
                for (W=0;W<1000;W=W+1)  
                 {
                   divisor++;
                 }
                
              }
            }
         }
   }
}

//---------------------------------------------------------------------------------------------------------------------
void espera1(void)
{
 
  for (S=0;S<1;S=S+1)
   {
       for (T=0;T<1;T=T+1)
         {
          
          for (U=0;U<900;U=U+1)
            {
              for (V=0;V<1000;V=V+1)
              {
                for (W=0;W<1000;W=W+1)  
                 {
                   divisor++;
                 }
                
              }
            }
         }
   }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
void setup() 
{
    pinMode(LED_BUILTIN, OUTPUT);  //Es el led Azul del chip
    Serial.begin(115200);
}


void loop() 
{
    digitalWrite(LED_BUILTIN, LOW);  //apaga el LED
    tiempo=millis();
    espera1();                         // 
    Serial.print(" Ha pasado : ");
    Serial.print(millis()-tiempo);
    Serial.print(" mS");

    
  
    digitalWrite(LED_BUILTIN, HIGH);   //enciende el LED
    tiempo=millis();
    espera2();
    Serial.print(". Segunda espera, han pasado : ");
    Serial.print(millis()-tiempo);
    Serial.println(" mS");
}

The text output through the serial port would be something like this :

Ha pasado : 397 mS. Segunda espera, han pasado : 0 mS
Ha pasado : 398 mS. Segunda espera, han pasado : 0 mS
Ha pasado : 396 mS. Segunda espera, han pasado : 0 mS
Ha pasado : 397 mS. Segunda espera, han pasado : 0 mS
Ha pasado : 398 mS. Segunda espera, han pasado : 1 mS

I strongly suspect that most of your code is being optimised away by the compiler as it does not actually do anything. Are you sure that the code in the functions is actually being executed ?

Using local and global variables with the same name also confuses the issue. Can you be sure that the expected versions of the variables are being used ?

Aside from that, local and global variables are stored in different ways so maybe a difference in the time to access them is to be expected

with the local variables, the optimiser has seen that you don't have any side effect so got rid of everything.

It could not do so with the global variables as you are accumulating through the loop the value into divisor (ie the second call adds to the first one).

so you are comparing code doing nothing to really 900 millions "increment" calls.

1 Like

Actually, at the core of the loops, increment the variable divisor -> divisor ++; really does something

If in the wait2 () function I use the global variable divisor, I would actually be doing something (increasing the variable global divisor, not local variable) but the execution time remains 0-1mS

It could be as you say, that the compiler optimizes the code, but I don't like the way it does it, without notifying me.

You could turn off optimizations and see what happens.

I feel the same way. So far, except for tests like you are conducting, I have never had an issue that turned out to involve optimization. So I learned to stop worrying and love it.

I assume a feeling that most ppl would be less happy w/o the compiler optimization led the powers that be to the default they selected.

a7

1 Like

No it has no side effects because it's not used for anything. ie if you don't do all the for loops, the behavior of the program stays the same so technically it's valid to get rid of everything.

intersting point about using the global variable in the second case. indeed it's still faster.

So what it means is that the optimiser possibly saw all the nested loops and was smart enough to just do

divisor += 9000000;

in the first case all the other variables are available in the loop and may be it was too much guessing for the optimiser to set those to the end of loop value.

have a look at the assembly code to see really what was generated

1 Like

First you are counting the first serial print as part of the time. You should get millis() right after the calls. As suspected above the espera2() routine is optimized out. See bold comments below
.
void loop()
{
digitalWrite(LED_BUILTIN, LOW); //apaga el LED
6d4: 80 e0 ldi r24, 0x00 ; 0
6d6: 0e 94 87 00 call 0x10e ; 0x10e <digitalWrite.constprop.3>
millis():
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:68
}

unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
6da: 2f b7 in r18, 0x3f ; 63
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:72

// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();

6dc: f8 94 cli
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:73
m = timer0_millis;
6de: 80 91 59 01 lds r24, 0x0159 ; 0x800159 <timer0_millis>
6e2: 90 91 5a 01 lds r25, 0x015A ; 0x80015a <timer0_millis+0x1>
6e6: a0 91 5b 01 lds r26, 0x015B ; 0x80015b <timer0_millis+0x2>
6ea: b0 91 5c 01 lds r27, 0x015C ; 0x80015c <timer0_millis+0x3>
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:74
SREG = oldSREG;
6ee: 2f bf out 0x3f, r18 ; 63
loop():
/home/dennis/sketchbook/sketch_dec10a/sketch_dec10a.ino:70
tiempo=millis();
6f0: 80 93 5d 01 sts 0x015D, r24 ; 0x80015d
6f4: 90 93 5e 01 sts 0x015E, r25 ; 0x80015e <tiempo+0x1>
6f8: a0 93 5f 01 sts 0x015F, r26 ; 0x80015f <tiempo+0x2>
6fc: b0 93 60 01 sts 0x0160, r27 ; 0x800160 <tiempo+0x3>
700: 60 91 51 01 lds r22, 0x0151 ; 0x800151
704: 70 91 52 01 lds r23, 0x0152 ; 0x800152 <V+0x1>
708: e0 91 4f 01 lds r30, 0x014F ; 0x80014f
70c: f0 91 50 01 lds r31, 0x0150 ; 0x800150 <W+0x1>
710: 80 91 4d 01 lds r24, 0x014D ; 0x80014d
714: 90 91 4e 01 lds r25, 0x014E ; 0x80014e <divisor+0x1>
718: 25 e8 ldi r18, 0x85 ; 133
71a: 33 e0 ldi r19, 0x03 ; 3
71c: 21 50 subi r18, 0x01 ; 1
71e: 31 09 sbc r19, r1

Code generated for espera1

espera1():
/home/dennis/sketchbook/sketch_dec10a/sketch_dec10a.ino:44
for (S=0;S<1;S=S+1)
{
for (T=0;T<1;T=T+1)
{

      for (U=0;U<900;U=U+1)

720: 51 f0 breq .+20 ; 0x736 <main+0x198>
722: 49 ee ldi r20, 0xE9 ; 233
724: 53 e0 ldi r21, 0x03 ; 3
726: 68 ee ldi r22, 0xE8 ; 232
728: 73 e0 ldi r23, 0x03 ; 3
72a: 41 50 subi r20, 0x01 ; 1
72c: 51 09 sbc r21, r1
/home/dennis/sketchbook/sketch_dec10a/sketch_dec10a.ino:46
{
for (V=0;V<1000;V=V+1)
72e: b1 f3 breq .-20 ; 0x71c <main+0x17e>
730: e8 ee ldi r30, 0xE8 ; 232
732: f3 e0 ldi r31, 0x03 ; 3
734: fa cf rjmp .-12 ; 0x72a <main+0x18c>
736: f0 93 50 01 sts 0x0150, r31 ; 0x800150 <W+0x1>
73a: e0 93 4f 01 sts 0x014F, r30 ; 0x80014f
73e: 97 51 subi r25, 0x17 ; 23
740: 90 93 4e 01 sts 0x014E, r25 ; 0x80014e <divisor+0x1>
744: 80 93 4d 01 sts 0x014D, r24 ; 0x80014d
748: 70 93 52 01 sts 0x0152, r23 ; 0x800152 <V+0x1>
74c: 60 93 51 01 sts 0x0151, r22 ; 0x800151
750: f0 92 54 01 sts 0x0154, r15 ; 0x800154 <U+0x1>
754: e0 92 53 01 sts 0x0153, r14 ; 0x800153
758: d0 93 56 01 sts 0x0156, r29 ; 0x800156 <T+0x1>
75c: c0 93 55 01 sts 0x0155, r28 ; 0x800155
760: d0 93 58 01 sts 0x0158, r29 ; 0x800158 <S+0x1>
764: c0 93 57 01 sts 0x0157, r28 ; 0x800157
write():
768: 82 e1 ldi r24, 0x12 ; 18
76a: 91 e0 ldi r25, 0x01 ; 1
76c: 0e 94 dc 01 call 0x3b8 ; 0x3b8 <Print::write(char const*) [clone .part.2] [clone .constprop.11]>
millis():
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:68
}

unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
770: 2f b7 in r18, 0x3f ; 63
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:72

// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();

772: f8 94 cli
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:73
m = timer0_millis;
774: 60 91 59 01 lds r22, 0x0159 ; 0x800159 <timer0_millis>
778: 70 91 5a 01 lds r23, 0x015A ; 0x80015a <timer0_millis+0x1>
77c: 80 91 5b 01 lds r24, 0x015B ; 0x80015b <timer0_millis+0x2>
780: 90 91 5c 01 lds r25, 0x015C ; 0x80015c <timer0_millis+0x3>
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:74
SREG = oldSREG;
784: 2f bf out 0x3f, r18 ; 63
loop():

No code generated for espera2

digitalWrite(LED_BUILTIN, HIGH);   //enciende el LED

7ac: 81 e0 ldi r24, 0x01 ; 1
7ae: 0e 94 87 00 call 0x10e ; 0x10e <digitalWrite.constprop.3>
millis():
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:68
}


unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;
 7b2:	2f b7       	in	r18, 0x3f	; 63
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:72

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
 7b4:	f8 94       	cli
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:73
	m = timer0_millis;
 7b6:	80 91 59 01 	lds	r24, 0x0159	; 0x800159 <timer0_millis>
 7ba:	90 91 5a 01 	lds	r25, 0x015A	; 0x80015a <timer0_millis+0x1>
 7be:	a0 91 5b 01 	lds	r26, 0x015B	; 0x80015b <timer0_millis+0x2>
 7c2:	b0 91 5c 01 	lds	r27, 0x015C	; 0x80015c <timer0_millis+0x3>
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:74
	SREG = oldSREG;
 7c6:	2f bf       	out	0x3f, r18	; 63
loop():
/home/dennis/sketchbook/sketch_dec10a/sketch_dec10a.ino:79
    tiempo=millis();
 7c8:	80 93 5d 01 	sts	0x015D, r24	; 0x80015d <tiempo>
 7cc:	90 93 5e 01 	sts	0x015E, r25	; 0x80015e <tiempo+0x1>
 7d0:	a0 93 5f 01 	sts	0x015F, r26	; 0x80015f <tiempo+0x2>
 7d4:	b0 93 60 01 	sts	0x0160, r27	; 0x800160 <tiempo+0x3>
write():
 7d8:	84 e2       	ldi	r24, 0x24	; 36
 7da:	91 e0       	ldi	r25, 0x01	; 1
 7dc:	0e 94 dc 01 	call	0x3b8	; 0x3b8 <Print::write(char const*) [clone .part.2] [clone .constprop.11]>
millis():
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:68
}

unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;
 7e0:	2f b7       	in	r18, 0x3f	; 63
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:72

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
 7e2:	f8 94       	cli
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:73
	m = timer0_millis;
 7e4:	60 91 59 01 	lds	r22, 0x0159	; 0x800159 <timer0_millis>
 7e8:	70 91 5a 01 	lds	r23, 0x015A	; 0x80015a <timer0_millis+0x1>
 7ec:	80 91 5b 01 	lds	r24, 0x015B	; 0x80015b <timer0_millis+0x2>
 7f0:	90 91 5c 01 	lds	r25, 0x015C	; 0x80015c <timer0_millis+0x3>
/home/dennis/arduino-1.8.13/hardware/arduino/avr/cores/arduino/wiring.c:74
	SREG = oldSREG;
 7f4:	2f bf       	out	0x3f, r18	; 63
loop():
1 Like

It looks like it really is a compiler optimization. I think there should be some simple possibility to control optimization in the IDE environment or in the preferences.txt file.

Thank you all for your quick and accurate response. :clap: :clap: :clap:

You can change the optimization - but it is not simple. You have the go to the platform.txt file for the board and modify it. For the brave...

The default of -Os is probably best in most cases. The -O3 may help if speed is of primary concern - but if you are that close to the edge you may want to reconsider your design.

If changing the optimization changes how your code behaves (other than speed) your code is 99.99% probably broken. The only case where I have seen optimization problems was on a new processor where the compiler group was writing the compiler as we were developing code. In that case it was only 98% probable that our code was bad. The problems that did show up were in the dark corner cases.

No, with GCC it's really simple. Only insert a line at the top of your code:

#pragma GCC optimize "O0"   // don't optimize

And if you do so, you will see, that both functions need very very much more time. You have to shorten the for loops to see any result at all. ( or you have to wait .... )

You can experiment with different optimizations to see the result - it's very interesting :wink:

another way to see the functions to take a lot of time would be to declare

volatile unsigned long divisor=0;

with volatile the optimisation are gone and your code will be.. let's say... "quite slower" I would think

Cool - I did not know that. If you add #pragma GCC optimize "O3" to the top of the code both subroutines are optimized out,