简体   繁体   English

Visual C ++ x86上的volatile变量和原子操作

[英]volatile variable and atomic operations on Visual C++ x86

Plain load has acquire semantics on x86, plain store has release semantics, however compiler still can reorder instructions. 普通加载在x86上获得了语义,普通商店具有发布语义,但编译器仍然可以重新排序指令。 While fences and locked instructions (locked xchg, locked cmpxchg) prevent both hardware and compiler from reordering, plain loads and stores are still necessary to protect with compiler barriers. 虽然围栏和锁定指令(锁定的xchg,锁定的cmpxchg)会阻止硬件和编译器重新排序,但仍需要普通的加载和存储来保护编译器障碍。 Visual C++ provides _ReadWriterBarrier() function, which prevents compiler from reordering, also C++ provides volatile keyword for the same reason. Visual C ++提供了_ReadWriterBarrier()函数,它可以防止编译器重新排序,同样C ++提供volatile关键字也是出于同样的原因。 I write all this information just to make sure that I get everything right. 我写这些信息只是为了确保我把一切都弄好。 So all written above is true, is there any reason to mark as volatile variables which are going to be used in functions protected with _ReadWriteBarrier()? 所以上面写的都是真的,有没有理由将其标记为将在_ReadWriteBarrier()保护的函数中使用的volatile变量?

For example: 例如:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

Is it safe to make that variable non-volatile? 使变量非易失性是否安全? As far as I understand it is, because function is protected and no reordering could be done by compiler inside. 据我所知,因为函数受到保护,内部编译器无法进行重新排序。 On the other hand Visual C++ provides special behavior for volatile variables (different from the one that standard does), it makes volatile reads and writes atomic loads and stores, but my target is x86 and plain loads and stores are supposed to be atomic on x86 anyway, right? 另一方面,Visual C ++为volatile变量提供了特殊的行为(不同于标准的变量),它使得volatile可以读取和写入原子载荷和存储,但我的目标是x86,而且x86上的普通加载和存储应该是原子的无论如何,对吗?

Thanks in advance. 提前致谢。

Volatile keyword is available in C too. Volatile也提供易失性关键字。 "volatile" is often used in embedded System, especially when value of the variable may change at any time-without any action being taken by the code - three common scenarios include reading from a memory-mapped peripheral register or global variables either modified by an interrupt service routine or those within a multi-threaded program. “volatile”通常用于嵌入式系统,特别是当变量的值可能随时发生变化时 - 代码不采取任何操作 - 三种常见方案包括从内存映射外设寄存器读取或全局变量修改中断服务程序或多线程程序中的程序。

So it is the last scenario where volatile could be considered to be similar to _ReadWriteBarrier. 因此,最后一种情况是volatile可以被认为与_ReadWriteBarrier类似。

_ReadWriteBarrier is not a function - _ReadWriteBarrier does not insert any additional instructions, and it does not prevent the CPU from rearranging reads and writes— it only prevents the compiler from rearranging them. _ReadWriteBarrier不是函数 - _ReadWriteBarrier不插入任何附加指令,并且它不会阻止CPU重新排列读取和写入 - 它只会阻止编译器重新排列它们。 _ReadWriteBarrier is to prevent compiler reordering. _ReadWriteBarrier是为了防止编译器重新排序。

MemoryBarrier is to prevent CPU reordering! MemoryBarrier是为了防止CPU重新排序!

A compiler typically rearranges instructions... C++ does not contain built-in support for multithreaded programs so the compiler assumes the code is single-threaded when reordering the code. 编译器通常会重新排列指令... C ++不包含对多线程程序的内置支持,因此编译器假定在重新排序代码时代码是单线程的。 With MSVC use _ReadWriteBarrier in the code, so that the compiler will not move reads and writes across it. 使用MSVC在代码中使用_ReadWriteBarrier,这样编译器就不会在其上移动读写操作。

Check this link for more detailed discussion on those topics http://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx 有关这些主题的更多详细讨论,请查看此链接http://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx

Regarding your code snippet - you do not have to use ReadWriteBarrier as a SYNC primitive - the first call to _ReadWriteBarrier is not necessary. 关于你的代码片段 - 你不必使用ReadWriteBarrier作为SYNC原语 - 第一次调用_ReadWriteBarrier是没有必要的。

When using ReadWriteBarrier you do not have to use volatile 使用ReadWriteBarrier时,您不必使用volatile

You wrote "it makes volatile reads and writes atomic loads and stores" - I don't think that is OK to say that, Atomicity and volatility are different. 你写道“它使得易失性读写原子载荷和存储” - 我不认为可以这么说,原子性和波动性是不同的。 Atomic operations are considered to be indivisible - ... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml 原子操作被认为是不可分割的 - ... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

Note: I am not an expert on this topic, some of my statements are "what I heard on the internet", but I think I csan still clear up some misconceptions. 注意:我不是这个主题的专家,我的一些陈述 “我在互联网上听到的”,但我认为我仍然清除了一些误解。

[edit] In general, I would rely on platform-specifics such as x86 atomic reads and lack of OOOX only in isolated, local optimizations that are guarded by an #ifdef checking the target platform, ideally accompanied by a portable solution in the #else path. [编辑]一般情况下,我会依赖于平台特定的,例如x86原子读取和缺少OOOX仅在隔离的本地优化中, #ifdef检查目标平台,理想情况下伴随着#else的便携式解决方案路径。

Things to look out for 值得关注的事情

  • atomicity of read / write operations 读/写操作的原子性
  • reordering due to compiler optimizations (this includes a different order seen by another thread due to simple register caching) 由于编译器优化而重新排序(这包括由于简单的寄存器缓存而被另一个线程看到的不同顺序)
  • out-of-order execution in the CPU CPU中的乱序执行

Possible misconceptions 可能存在误解

1. As far as I understand it is, because function is protected and no reordering could be done by compiler inside. 1. 据我所知,因为函数受到保护,内部编译器无法进行重新排序。
[edit] To clarify: the _ReadWriteBarrier provides protection against instruction reordering, however, you have to look beyond the scope of the function. [编辑]澄清_ReadWriteBarrier_ReadWriteBarrier提供了对指令重新排序的保护,但是,你必须超越函数的范围。 _ReadWriteBarrier has been fixed in VS 2010 to do that, earlier versions may be broken (depending on the optimizations they actually do). _ReadWriteBarrier已经在VS 2010中修复了,早期版本可能会被破坏(取决于他们实际做的优化)。

Optimization isn't limited to functions. 优化不仅限于功能。 There are multiple mechanisms (automatic inlining, link time code generation) that span functions and even compilation units (and can provide much more significant optimizations than small-scoped register caching). 有多种机制(自动内联,链接时间代码生成)跨越函数甚至编译单元(并且可以提供比小范围寄存器缓存更重要的优化)。

2. Visual C++ [...] makes volatile reads and writes atomic loads and stores, 2. Visual C ++ [...]使得易失性读写原子载荷和存储,
Where did you find that? 你是在哪里找到那个东西的。 MSDN says that beyond the standard, will put memory barriers around reads and writes, no guarantee for atomic reads. MSDN表示,超出标准,会在读写时产生内存障碍,无法保证原子读取。

[edit] Note that C#, Java, Delphi etc. have different memory mdoels and may make different guarantees. [编辑]请注意,C#,Java,Delphi等具有不同的内存mdoels,可能会有不同的保证。

3. plain loads and stores are supposed to be atomic on x86 anyway, right? 3. 无论如何,普通的加载和存储应该是x86上的原子,对吧?
No, they are not. 不,他们不是。 Unaligned reads are not atomic. 未对齐的读取不是原子的。 They happen to be atomic if they are well-aligned - a fact I'd not rely on unless it's isolated and easily exchanged. 如果它们完全一致,它们恰好是原子的 - 除非它是孤立的并且易于交换,否则我不会依赖它。 Otherwise your "simplificaiton fo x86" becomes a lockdown to that target. 否则,你的“x86简化”将成为该目标的锁定。

[edit] Unaligned reads happen: [编辑]未对齐的读取发生:

char * c = new char[sizeof(int)+1];
load(*(int *)c);      // allowed by standard to be unaligned
load(*(int *)(c+1));  // unaligned with most allocators

#pragma pack(push,1)
struct 
{
   char c;
   int  i;
} foo;
load(foo.i);         // caller said so
#pragma pack(pop)

This is of course all academic if you remember the parameter must be aligned, and you control all code. 如果您记得参数必须对齐,并且您控制所有代码,那么这当然是学术性的。 I wouldn't write such code anymore, because I've been bitten to often by laziness of the past. 我不会再写这样的代码,因为我经常被过去的懒惰所困扰。

4. Plain load has acquire semantics on x86, plain store has release semantics 4. 普通加载在x86上获取语义,普通商店具有释放语义
No. x86 processors do not use out-of-order execution (or rather, no visible OOOX - I think), but this doesn't stop the optimizer from reordering instructions. 不.X86处理器不使用无序执行(或者更确切地说,没有可见的OOOX - 我认为),但这并不能阻止优化器重新排序指令。

5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier do all the magic They don't - they just prevent reordering by the optimizer. 5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier做了所有的魔术它们没有 - 它们只是阻止优化器的重新排序。 MSDN finally made it a big bad warning for VS2010, but the information apparently applies to previous versions as well . MSDN最终对VS2010 发出严重的警告 ,但这些信息显然也适用于以前的版本


Now, to your question. 现在,问你的问题。

I assume the purpose of the snippet is to pass any variable N, and load it (atomically?) The straightforward choice would be an interlocked read or (on Visual C++ 2005 and later) a volatile read. 我假设片段的目的是传递任何变量N,并加载它(原子地?)直接的选择是互锁读取或(在Visual C ++ 2005及更高版本上)易失性读取。

Otherwise you'd need a barrier for both compiler and CPU before the read, in VC++ parlor this would be: 否则,在读取之前,你需要为编译器和CPU提供一个屏障,在VC ++中,这将是:

int load(int& var)
{   
  // force Optimizer to complete all memory writes:
  // (Note that this had issues before VC++ 2010)
   _WriteBarrier();    

  // force CPU to settle all pending read/writes, and not to start new ones:
   MemoryBarrier();

   // now, read.
   int value = var;    
   return value;
}

Noe that _WriteBarrier has a second warning in MSDN: *In past versions of the Visual C++ compiler, the _ReadWriteBarrier and _WriteBarrier functions were enforced only locally and did not affect functions up the call tree. Noe, _WriteBarrier在MSDN中有第二个警告:*在以前版本的Visual C ++编译器中,_ReadWriteBarrier和_WriteBarrier函数仅在本地强制实施,并不影响调用树的功能。 These functions are now enforced all the way up the call tree.* 现在,这些函数一直强制执行调用树。*


I hope that is correct. 希望这是正确的。 stackoverflowers, please correct me if I'm wrong. 如果我错了,请纠正我。

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

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