简体   繁体   English

什么样的优化在C ++中“易失”?

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

I was looking up the keyword volatile and what it's for, and the answer I got was pretty much: 我正在查找关键字volatile以及它的用途,我得到的答案非常多:

It's used to prevent the compiler from optimizing away code. 它用于防止编译器优化代码。

There were some examples, such as when polling memory-mapped hardware: without volatile the polling loop would be removed as the compiler might recognize that the condition value is never changed. 有一些例子,例如轮询内存映射硬件:没有volatile ,轮询循环将被删除,因为编译器可能会认识到条件值永远不会改变。 But since there only were one example or maybe two, it got me thinking: Are there other situations where we need to use volatile in terms of avoiding unwanted optimization? 但由于只有一个例子或两个例子,它让我思考:在避免不必要的优化方面,我们是否还需要使用volatile Are condition variables the only place where volatile is needed? 条件变量是唯一需要volatile的地方吗?

I imagine that optimization is compiler-specific and therefore is not specified in the C++ specification. 我认为优化是特定于编译器的,因此未在C ++规范中指定。 Does that mean we have to go by gut feeling, saying Hm, I suspect my compiler will do away with this if I don't declare that variable as volatile or are there any clear rules to go by? 这是否意味着我们必须通过直觉,说嗯,我怀疑我的编译器将废除这个,如果我不宣布该变量为volatile或有任何明确的规则可以通过?

Basically, volatile announces that a value might change behind your program's back. 基本上, volatile宣布一个值可能会在你的程序背后发生变化。 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. 这可以防止编译器缓存该值(在CPU寄存器中),并防止在程序的POV中看起来不必要时访问该值。

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. 什么应该触发volatile使用是当一个值改变时,尽管你的程序没有写入它,并且没有其他内存障碍(如用于多线程程序的互斥锁)。

The observable behavior of a C++ program is determined by read and writes to volatile variables, and any calls to input/output functions. C ++程序的可观察行为取决于对volatile变量的读写,以及对输入/输出函数的任何调用。

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. 这需要的是,对volatile变量的所有读写都必须按照它们在代码中出现的顺序发生,并且它们必须发生。 (If a compiler broke one of those rules, it would be breaking the as-if rule.) (如果编译器违反了其中一条规则,则会破坏as-if规则。)

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.) (注意, “C ++和双重锁定的危险”文章对此有所了解。)


So to answer the title question, it prevents any optimization that might re-order the evaluation of volatile variables relative to other volatile variables . 因此,为了回答标题问题,它可以防止任何可能重新排序相对于其他volatile变量的volatile变量评估的优化。

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). 很好,因为x的值不是可观察行为的一部分(它不是易变的)。 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. 什么是不错的是将赋值从5更改为赋值为7,因为写5是一个可观察的效果。

Condition variables are not where volatile is needed; 条件变量不是需要volatile的地方; strictly it is only needed in device drivers. 严格来说,它只在设备驱动程序中需要。

volatile guarantees that reads and writes to the object are not optimized away, or reordered with respect to another volatile . volatile保证对对象的读取和写入不会被优化掉,或者针对另一个volatile重新排序。 If you are busy-looping on a variable modified by another thread, it should be declared volatile . 如果您正在忙于循环另一个线程修改的变量,则应将其声明为volatile However, you shouldn't busy-loop. 但是,你不应该忙着循环。 Because the language wasn't really designed for multithreading, this isn't very well supported. 由于该语言并非真正为多线程设计,因此不太受支持。 For example, the compiler may move a write to a non -volatile variable from after to before the loop, violating the lock. 例如,编译器可以在循环之前从移动后的写入到 -volatile变量,违反了锁。 (For indefinite spinloops, this might only happen under C++0x.) (对于无限旋转,这可能只发生在C ++ 0x下。)

When you call a thread-library function, it acts as a memory fence, and the compiler will assume that any and all values have changed — essentially everything is volatile. 当您调用线程库函数时,它充当内存栅栏,编译器将假定所有值都已更改 - 基本上所有内容都是volatile。 This is either specified or tacitly implemented by any threading library to keep the wheels turning smoothly. 这可以由任何线程库指定或默认实现,以保持车轮平稳转动。

C++0x might not have this shortcoming, as it introduces formal multithreading semantics. C ++ 0x可能没有这个缺点,因为它引入了正式的多线程语义。 I'm not really familiar with the changes, but for the sake of backward compatibility, it doesn't require to declare anything volatile that wasn't before. 我并不熟悉这些更改,但为了向后兼容,它不需要声明之前没有的任何volatile。

Volatile doesn't try to keep data to a cpu register (100's of times faster than memory). Volatile不会尝试将数据保存到cpu寄存器(比内存快100倍)。 It has to read it from memory every time it is used. 它必须在每次使用时从内存中读取它。

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, eg 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: 根本不需要xy ,但可以替换为:

return 10;

And another example, is that any code that doesn't affect state from the outside could be removed entirely. 另一个例子是,任何不影响外部状态的代码都可以完全删除。 Eg 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可以用来阻止这种情况发生。

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". volatile可以被认为是“这个变量的状态必须被视为外部可见状态的一部分,而不是被搞乱”。 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). (注意C#。我最近看到很多关于volatile表明人们正在阅读有关C ++ volatile并将其应用于C#,并在C#中阅读它并将其应用于C ++。实际上, volatile之间的表现如此不同两个对于考虑它们没有用的相关)。

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 除非你在嵌入式系统上,或者你正在编写硬件驱动程序,其中内存映射被用作通信手段,否则你永远不应该使用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: 而没有volatile可能是:

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. 关于volatile的假设是可以改变变量的存储位置。 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. 并告诉编译器不允许在寄存器中重用该值。

One way to think about a volatile variable is to imagine that it's a virtual property; 考虑volatile变量的一种方法是想象它是一个虚拟属性; 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; 用于写入/读取volatile变量的实际生成代码只是内存写入或读取(*),但编译器必须将代码视为不透明; 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. (*)在某些编译器中,可以将volatile变量作为不同的操作添加,减去,递增,递减等。 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). 因为后一种形式在许多微处理器上可能是原子的(尽管不是在现代多核PC上)。 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 如果'volatilevar1'不介意被阅读两次并且'volatilevar2'不介意在volatilevar1之前写入,那么将语句拆分为

volatilevar2 = volatilevar1;
  volatilevar1++;

would allow for faster, and possibly safer, code. 将允许更快,更安全的代码。

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. 除非程序员明确地使用volatile关键字指示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM