Memory barrier


#1

memory barrier啊? 这个是在多核多线程环境下需要的东西。 分为编译期间 memory barrier 和运行时 memory barrier

在 c++11 之前,实现编译期 memory barrier 的办法就是 volatile 关键字。 不过也就仅此而已了 控制不到运行时的 mb.

mb 这东西啊,简单的来说就是观察者一致性。

对于给定内存地址上的数据,任何 cpu 读取到的内容都是一致的。

咋一看好像本来就是这样的啊,实际上不是的 cpu有缓存。 cpu 每个核心都有自己独立的缓存。 这样同样的数据,可能就在不同的核心缓存里有多份拷贝

mb 就是用来维护这些缓存数据的一致性 这是硬件上的 mb

编译期 mb 说的就是寄存器。 编译器会将变量的数据缓存到寄存器上。那变量本身已经被其他核心或者其他硬件(MMIO的情况)修改了 寄存器里的那份变量就没有一致性了

volatile 关键字就是让编译器每次都从内存读取而放弃使用寄存器,来实现一致性。

但是这个要求没办法传递给 cpu 啊! 所以这个关键字在 c++11 里被认为是不适合用来实现 mb

x86 cpu 是隐式 mb 的。 cpu 从硬件上自动保证 cpu 对内存访问的一致性。 但是 arm 就不同了 需要使用 mb 指令。强制 cpu 刷新缓存。

你必须使用 mb 指令告诉 cpu, 访问这块数据的时候,别从缓存载入数据,一定要从 内存载入

写入的时候 mb 指令告诉 cpu 一定要写回内存,而不是缓存。 而 x86 是在硬件上自动保证的。没有专门的 mb 指令

所以在 x86 上, mb 更多的是对编译器的指示。 在 arm 上,那就即是 对编译器的指示,又是对 cpu 本身的指示。

对 编译器来说, wmb 指令就是告诉编译器,到这里的时候,把生成指令把还未写入入内存的寄存器数据给写了。 也就是执行一次 reg -> mem 的同步。

因为在一个函数体内多次修改的变量, 编译器可能并不会生成多次写内存指令。而是在寄存器里多次修改而已。 于是如果你修改了变量,那么当前线程看到的变量的值(寄存器里)和其他线程看到的(内存上)就不一致了

wmb 告诉编译器生成写指令,同步一下。 同样的, 一个函数体内多次读取一个变量的值,那么编译器就会使用寄存器来优化访问,不会每次都从内存读取。

这时候如果其他线程修改了变量,咋办? 就要重新读取。 指示编译器生产重新读取指令就是 rmb。

mb 指令相比 volatile 而言,精确的告诉了编译器,这次寄存器刷新操作的目的。 相比 volatile 能生成更好的代码。

另外,在 C/C++ 里,调用全局的函数隐含一次编译期 mb 操作。 除非函数被标记为 pure 函数。 pure 函数就是返回值只依赖 参数的函数 C 标准看的 math 函数 就是 pure 函数的例子。

调用 pure 函数编译器不会插入隐含的 mb 操作。 如何将一个函数标记为 pure 函数? 就是使用 const 修饰。

double sin(double) const;

这样声明的 sin 函数就是 pure 函数了 以上!


#2

这是个巨险恶的主题,我等平庸之辈只敢在std::atomic建立的抽象层上玩,内存序全取默认的。当然 volatile 也是必需的。


#3

x86 cpu 是隐式 mb 的。 cpu 从硬件上自动保证 cpu 对内存访问的一致性。 但是 arm 就不同了 需要使用 mb 指令。强制 cpu 刷新缓存。

你必须使用 mb 指令告诉 cpu, 访问这块数据的时候,别从缓存载入数据,一定要从 内存载入

写入的时候 mb 指令告诉 cpu 一定要写回内存,而不是缓存。 而 x86 是在硬件上自动保证的。没有专门的 mb 指令 <============================================================> 描述的x86的不准确吧。x86依然存在stroe load的乱序。可以参考http://preshing.com/files/ordering.zip 演示store-load的乱序。加入阻断乱序指令后,就解决了。事实上x86 提及的遗憾mb表诉不太正确。虽然x86算是很宽松的内存模型。依然存在乱序的。

THREAD1 THREAD2 Y = 1; X = 1; r2 = X; r1 = Y;

事实存在r2=r1=0的情况。但是Y=1和X=1后加入mb后r2=r1=0不会发生。so 我觉得博士描述的x86是隐含的mb不太准确.


#4

编译期 mb 和 cpu运行时 mb.

你可能指的是编译器生成的指令没有正确的排序好指令.

没有 mb 的话, 编译器实际上可以自由的安排 r2=x 和 y=1 这2条语句的位置.


#5

不是,你可以看给你说的那个下载地址。反汇编后,编译器没有打乱代码。而是CPU打乱的。因为代码是用voliate限制了编译器打乱。你可以下载这个代码 在gcc或者vs 下进行测试。 http://preshing.com/files/ordering.zip


#6

x86 内部隐式 mb 是 intel 的说法.

为了可移植性考虑, 任何情况下都要以 cpu 不会隐式 mb 来考量. 即便未来某个时候 x86 变得同样需要显式发出 mb 指令(这种情况很有可能已经发生了), 也不至于惊慌失措了.


#7

那个叫做依赖mb,如果要访问的变量在流水线上存在dep的情况,那么才是你说的隐含mb…你应该指的是Dependent loads reordered。。但是我认为你的表述 x86 隐含mb 不是很准确。事实上可能有一些误导。


#8

x86 内部隐式 mb 是 intel 的说法, 你可以找下 intel 的手册. x86 可以认为 cache 是透明的, 不存在的, 所有的操作都是立即写回内存的. 这样就不需要使用 mb 指令刷新缓存.

mb 指令的作用是 刷新缓存, 指令前后禁止乱序执行.

然而 x86 的缓存是隐式 mb 的, 手册没说乱序执行是隐式的. x86 到现在也没清楚的说明自己的cpu是如何乱序执行的. 原则上 x86 并不含乱序执行. 现在的x86都有, 只能说是后来的优化.

在 x86 上防止乱序执行导致问题的 , 通常使用长调制指令来清空流水线. 强制清空流水线以避免乱序执行问题.


#9

按照你的说法,x86认为cache是透明的,所有操作都是立即回写内存的。那么这样是不是意味着BIOS上设置 write-through 和write-back 是无意义的了?且总是可以认为MESI来帮我们做了这事???


#10

你的理解能力是不是太有问题?

透明的意思是程序不用去操心.


#11

事实上,我给你找的那个示例程序为啥程序要去操心?如果你能修改到那个程序不操心CPU mb,我可以认为我的理解有问题,理解错了。but现在的问题就是那个程序事实上去操心了CPU的mb的操作。那么究竟是我理解的问题,还是你的理解的问题呢?


#12

我从来就没说你写 C++ 代码可以不操心 mb.

我只告诉你,x86 隐含的mb导致了许多没操心 mb 的代码"似乎工作的很好" 然而这些代码实际上都是错的. 所以 c++11 才要搞 mb.

你那代码没考虑 mb 然后就错了. 我说他是对的了吗?


#13

x86 可以认为 cache 是透明的, 不存在的, 所有的操作都是立即写回内存的. 这样就不需要使用 mb 指令刷新缓存. 你的理解能力是不是太有问题?

透明的意思是程序不用去操心.

============================================= 上面的话,不是我说的。


#14

我在告诉大家写多线程代码要考虑 mb

有的人会问 "我之前都没考虑, 也工作的很好啊!"

我的回答是. 那是你运气好, 在 x86 上跑.

这个有疑问吗? 你的理解成了 “x86上就不用 mb 啦!”

卧槽, 这是什么逻辑? 你的理解能力就是这样的吗?


#15

x86 你给我找条刷新缓存的指令出来. 人家压根就没提供这样的指令, 不是透明的你还狡辩啥.


#16

x86 cpu 是隐式 mb 的。 cpu 从硬件上自动保证 cpu 对内存访问的一致性。。

这句话,可不是让我们忘记mb存在的。


#17

lock 前缀的指令不刷cache吗?

玩不了了,只能回复3次。。。。。。。。。。。。。。。。。。


#18

这就话是解释 “为啥我写程序没考虑 mb 也没出错”

并不是叫你不用去考虑 mb


#19

x86 你给我找条刷新缓存的指令出来. 人家压根就没提供这样的指令, 不是透明的你还狡辩啥.

======

ms 也就是简单的用一个lock前缀来做的mb 。。在后面的cpu上才用mfence做的。

lock 可以让在其他CPU cache的数据被其它CPU看见。这不算刷cache pipe算啥?你还狡辩啥啊?只让回复3次,啥玩意啊


#20

arm 提供了专门的 chache flush 指令, 可以让当前 cpu 的缓存无效. x86 没有这样的指令.

LOCK 是锁总线用的. 避免其他 cpu 访问总线,这个是用来保证原子操作的. mb 和原子操作不是一回事.