What kinds of optimizations does 'volatile' prevent in C++?

后端 未结 8 1729
Happy的楠姐
Happy的楠姐 2020-11-27 17:04

I was looking up the keyword volatile and what it\'s for, and the answer I got was pretty much:

It\'s used to prevent the compiler from o

相关标签:
8条回答
  • 2020-11-27 17:28

    usually compiler assumes that a program is single threaded, therefore it has complete knowledge of what's happening with variable values. a smart compiler can then prove that the program can be transformed into another program with equivalent semantics but better performance. for example

    x = y+y+y+y+y;
    

    can be transformed to

    x = y*5;
    

    however, if a variable can be changed outside the thread, compiler doesn't have a complete knowledge of what's going on by simply examining this piece of code. it can no longer make optimizations like above. (edit: it probably can in this case; we need more sophisticated examples)

    by default, for performance optimization, single thread access is assumed. this assumption is usually true. unless programmer explicitly instruct otherwise with the volatile keyword.

    0 讨论(0)
  • 2020-11-27 17:29

    Basically, volatile announces that a value might change behind your program's back. That prevents compilers from caching the value (in a CPU register) and from optimizing away accesses to that value when they seem unnecessary from the POV of your program.

    What should trigger usage of volatile is when a value changes despite the fact that your program hasn't written to it, and when no other memory barriers (like mutexes as used for multi-threaded programs) are present.

    0 讨论(0)
  • 2020-11-27 17:34

    The observable behavior of a C++ program is determined by read and writes to volatile variables, and any calls to input/output functions.

    What this entails is that all reads and writes to volatile variables must happen in the order they appear in code, and they must happen. (If a compiler broke one of those rules, it would be breaking the as-if rule.)

    That's all. It's used when you need to indicate that reading or writing a variable is to be seen as an observable effect. (Note, the "C++ and the Perils of Double-Checked Locking" article touches on this quite a bit.)


    So to answer the title question, it prevents any optimization that might re-order the evaluation of volatile variables relative to other volatile variables.

    That means a compiler that changes:

    int x = 2;
    volatile int y = 5;
    x = 5;
    y = 7;
    

    To

    int x = 5;
    volatile int y = 5;
    y = 7;
    

    Is fine, since the value of x is not part of the observable behavior (it's not volatile). What wouldn't be fine is changing the assignment from 5 to an assignment to 7, because that write of 5 is an observable effect.

    0 讨论(0)
  • 2020-11-27 17:37

    Unless you are on an embedded system, or you are writing hardware drivers where memory mapping is used as the means of communication, you should never ever ever be using volatile

    Consider:

    int main()
    {
        volatile int SomeHardwareMemory; //This is a platform specific INT location. 
        for(int idx=0; idx < 56; ++idx)
        {
            printf("%d", SomeHardwareMemory);
        }
    }
    

    Has to produce code like:

    loadIntoRegister3 56
    loadIntoRegister2 "%d"
    loopTop:
    loadIntoRegister1 <<SOMEHARDWAREMEMORY>
    pushRegister2
    pushRegister1
    call printf
    decrementRegister3
    ifRegister3LessThan 56 goto loopTop
    

    whereas without volatile it could be:

    loadIntoRegister3 56
    loadIntoRegister2 "%d"
    loadIntoRegister1 <<SOMEHARDWAREMEMORY>
    loopTop:
    pushRegister2
    pushRegister1
    call printf
    decrementRegister3
    ifRegister3LessThan 56 goto loopTop
    

    The assumption about volatile is that the memory location of the variable may be changed. You are forcing the compiler to load the actual value from memory each time the variable is used; and you tell the compiler that reuse of that value in a register is not allowed.

    0 讨论(0)
  • 2020-11-27 17:38

    Remember that the "as if rule" means that the compiler can, and should, do whatever it wants, as long as the behaviour as seen from outside the program as a whole is the same. In particular, while a variable conceptually names an area in memory, there is no reason why it actually should be in memory.

    It could be in a register:

    Its value could be calculated away, e.g. in:

    int x = 2;
    int y = x + 7;
    return y + 1;
    

    Need not have an x and y at all, but could just be replaced with:

    return 10;
    

    And another example, is that any code that doesn't affect state from the outside could be removed entirely. E.g. if you zeroise sensitive data, the compiler can see this as a wasted exercise ("why are you writing to what won't be read?") and remove it. volatile can be used to stop that happening.

    volatile can be thought of as meaning "the state of this variable must be considered part of the outwardly visible state, and not messed with". Optimisations that would use it other than literally following the source code are not allowed.

    (A note C#. A lot I've seen of late on volatile suggests that people are reading about C++ volatile and applying it to C#, and reading about it in C# and applying it to C++. Really though, volatile behaves so differently between the two as to not be useful to consider them related).

    0 讨论(0)
  • 2020-11-27 17:40

    One way to think about a volatile variable is to imagine that it's a virtual property; writes and even reads may do things compiler can't know about. The actual generated code for a writing/reading a volatile variable is simply a memory write or read(*), but the compiler has to regard the code as opaque; it can't make any assumptions under which it might be superfluous. The issue isn't merely with making sure that the compiled code notices that something has caused a variable to change. On some systems, even memory reads can "do" things.

    (*) On some compilers, volatile variables may be added to, subtracted from, incremented, decremented, etc. as distinct operations. It's probably useful for a compiler to compile:

      volatilevar++;
    

    as

      inc [_volatilevar]
    

    since the latter form may be atomic on many microprocessors (though not on modern multi-core PCs). It's important to note, however, that if the statement were:

      volatilevar2 = (volatilevar1++);
    

    the correct code would not be:

      mov ax,[_volatilevar1] ; Reads it once
      inc [_volatilevar]     ; Reads it again (oops)
      mov [_volatilevar2],ax
    

    nor

      mov ax,[_volatilevar1]
      mov [_volatilevar2],ax ; Writes in wrong sequence
      inc ax
      mov [_volatilevar1],ax
    

    but rather

      mov ax,[_volatilevar1]
      mov bx,ax
      inc ax
      mov [_volatilevar1],ax
      mov [_volatilevar2],bx
    

    Writing the source code differently would allow the generation of more efficient (and possibly safer) code. If 'volatilevar1' didn't mind being read twice and 'volatilevar2' didn't mind being written before volatilevar1, then splitting the statement into

      volatilevar2 = volatilevar1;
      volatilevar1++;
    

    would allow for faster, and possibly safer, code.

    0 讨论(0)
提交回复
热议问题