Cost of Volatile variables?

I am just curious to know if there is any additional cost to a sketch - on any level, memory consumption probably being the most important as well as clock cycle consumption - when declaring a variable as being volatile vs. not declaring it as volatile.

From my experience, which is dated... If you look at the generated code, many variables are temporary and are manipulated on the stack... they sometimes refer to this as being 'optimized away' this is a problem when you trying to debug at a low level.

The compiler knows where this variable is, in a register or whatever and may fetch the value from someplace it's got handy.

Volatile tells the compiler to get it from where it lives and not a copy as it can change without the 'main' program knowing when..

If you are updating via an interrupt, you must use a volatile tag.

:smiley_cat:

Thats interesting, I didn't know variables were normally handled via copy and not direct reference.

The reason I ask the question is that I have a library that's published that offers a simple way to have non-blocking timers in a sketch. And as I've been working with Picos lately and engaging multi-threading, I began to wonder if I should be declaring the main variables in my library as volatile so that they are "thread-safe", but wasn't sure how that might impact the average use of the library, which would most certainly be in non-threaded code.

And I don't want to make any changes to the library that would adversely impact someone's sketch under normal use.

Yes, it defeats some optimization. So there may be a memory and/or speed penalty. But it will be almost insignificant, and only a small number of variables usually need to be declared 'volatile'.

We are talking, a few machine cycles or bytes per variable.

This made your question more complicated. Please post a link to the library. If you made classes, different threads would use different objects from the class, the variables would be different.

Re-entrancy is a little bit tricky, so there is no short general answer.

Here is the library

The main issue, of course, would be when making changes to an instantiated timer - in terms of a possible need for volatile variables. But I mostly think it would never be a problem... but it never hurts to cover as many bases as possible with these things.

Thanks, where you may have trouble is with the class members that you have declared 'static'. Those are shared by all instantiations of the class.

I think 'volatile' won't help you because a re-entrant program saves all its registers when it's interrupted, and uses no statically allocated memory. It does that regardless of whether variables are updated in memory or not. The real point is, whether the memory is shared.

I disagree. Blind protections that may or may not work clutter a program, and introduce additional hidden opportunities for failure.

Details?

I've been using the library recently with stepper motors where I need to step with X number of microseconds delayed ... and as I vary a foot pedal, the speed changes so the timers duration value gets changed sometimes constantly ...

in my case I use one thread to step the motors and the other thread to change the timer.

I could see a scenario where someone could be changing the duration of a timer from two different threads and being mainly a Java programmer, I have limited knowledge of C++ especially as it works with variables but in Java, if two threads try to change a variable at the same instant in time, that is not a "thread-safe" variable, it causes a problem - program stops running etc.

Just want to avoid any such possibilities with this library ... if it's even something that could be a problem at all.

Okay, thank you that is informative too, but I meant details of the CPU you mentioned and the multi-threading software.

Are you really just referring to the typical round-robin non-blocking cooperative multitasking that everyone uses?

Im using a Pi Pico for my recent project. I have a global declaration of multiple timers where the main thread reads the value of a foot pedal then calculates rpms for each motor in terms of delayed microseconds in between steps applied to multiple stepper motors.

In the second core, I have a loop that steps the motors using the global timers as the delay between steps. And since the timers are non blocking, I can step multiple motors very fluidly...

But as I said, as Ive been working on this project I began to wonder about the possibility that someone might write code that changes a global timer from different threads which could create a scenario where two threads try to change those values at the same instant in time and when those variables are not volatile ... it is my understanding that such a scenario would be problematic for a program.

If I'm wrong about that, then its a done deal, no need to make any changes to the library.

This is why I am asking you about the Pico threading implementation. I think most people here are not familiar with it at all. If the core allocates different memory blocks to different threads, then you have nothing to worry about.

If you can't, perhaps this question would get more answers in a Pi forum.

Declaring them 'volatile' does not make them "Thread-Safe". Writing proper tread-safe code makes them thread-safe.

BTW, even the simplest AVR code can have multiple "threads" when you consider interrupt context. Just declaring variables used in both interrupt and non-interrupt code as 'volatile' is insufficient to make them thread-safe. You must ensure that access to them is atomic in both "threads".

2 Likes

Isn't class encapsulation supposed to prevent things like that?

Hardware resources should be protected by drivers in the operating system. Other shared resources (variables...) can be protected by privileged services. This way no thread or program code has to bother itself with atomic access to shared variables or resources.

All these thoughts are subject to multi core controllers with true concurrent access. On smaller controllers an OS with threads is typically out of reach. This is where variables with ISR access should be protected by the volatile attribute and atomic access. Proper use of volatile has to guarantee that lambda functions are not compromised by values changed during formula evaluation. This can be done by local copies of volatile variables which are made only at the begin of a logical code block.

Oh I understand what you're asking now. And I have no knowledge about the Pico on that level. What I do know is how to add code that executes from the other core, which is done like this:

[[noreturn]] void core1Entry() {
    //Core1 code executes here
    while (true) {
        if (stepperTimer1.TRIGGERED)
            stepStepper1();
        
        if (stepperTimer2.TRIGGERED)
            stepStepper2();
        
        if (stepperTimer3.TRIGGERED)
            stepStepper3();
    }
}

void setup() {
    Serial.begin(115200);
    setPins();
    multicore_launch_core1(core1Entry);
}

void loop() {
    //Core0 code executes here
}

Also, it is the Picos documentation that specifically makes mention of the need to declare variables as volatile if there are going to be changes made to a variable from both cores.

Now you tell us. I've been sort of pleading for a pointer to that documentation in reply #10, 12, 14...

Do you have a link to, or excerpt from that section?