多核环境下的内存屏障指令
本来不打算立刻写关于这次 软件开发大会 的事情。太多可以写的东西,反而不知道怎么写起。今天才有机会上网到处转转,转到 周伟民老师 的 blog 上,看到这么一篇 。里面既然提到我,就想在上面回上两句。可惜 csdn 的 blog 系统实在是太烂了(这个话题我们在周六的沙龙上集体声讨过,暂且按下不表),硬是没发上留言。那么我还是在自己的地盘单独提出来说说吧。
周老师那个 session 正好排在我的前面。同一间会议室,而且内容我也颇有兴趣。也就顺理成章的听了。讲的东西其实满不错的,唯一的抱怨是太像在大学里授课,互动少了点。会场气氛远不如后来 Andrei 讲 Lock-Free Data Structures 那么精彩。
周老师讲的这块内容,正巧我前几年写多线程安全的内存分配器时碰到过,有点研究。加上前几年对 Intel 的东西颇有兴趣,便有了发言的冲动 :) 。当时的会场上下文环境正好是有个朋友提问说:实际上,InterlockedIncrement 的调用是多余的。(事后交换名片得知,提问的这个哥们是来至 google 的程序员)
如果换在几年前,我是赞同这个哥们的观点的。记得 04 年左右,我们公司内部的 maillist 上曾经有类似的讨论。即,在 32 位系统上,写一个 dword 本身就是原子的,如果 cpu 可以保证程序逻辑上的执行次序(program ordering),那么简单的利用写操作就可以替代锁操作。我们在操作完一大片内存数据后,只需要在最后更改关键的标记字,那么不需要加锁也可以保证安全。(还有一个隐含的前提是数据必须被 32bit 对齐)
btw, 当天晚上 Andrei 讲 Lock-Free Data Structures 时向大家提的问题:那个 hazard list 为什么要用单向链表实现?大约也是这个意思,因为链表指针可以被原子的修改而无需加锁。
单核时代它是对的,因为单核 CPU 要求读写操作 self-consistent 。多嘴两句解释一下,现代 CPU 工作时的指令执行次序(process ordering)是可以不同于程序编制的次序(program ordering)的,即乱序执行技术。这个技术可以极大的提高流水线的工作效率。单核 CPU 保证读写操作的 self-consistent 意味着,等到真正读入操作数据时,数据符合 program ordering 上的正确性。
可问题也出在这里。随着多核的发展,为了提高每个核上的流水线效率,多核环境不再保证其安全。在每一个核上,cpu 内部工作时指令都可能被乱序执行。那么逻辑次序上后写入内存的数据未必真的最后写入。核与核之间作为一个整体看的话,却不保证 self-consistent 了。
也就是说,如果你不做多余的防护措施,在一个核上写入一批数据后,如果你期望最后写一个标记数据表示前面的数据都已经准备好;然后从另一个核上依靠判断这个标记位来判定一切数据就绪。这个策略并不可靠。标记位可能被先写入,但其它数据尚未真正写入内存。
解决的方法是,在标记位被写入前,强迫 CPU 串行化。InterlockedIncrement 和它的兄弟们可以提供这种安全性。翻译成 Intel 指令,会发现它在汇编指令前加了 lock 前缀。也就是在这些读写内存的指令在发起时,cpu 会向总线发出一个 lock# 的信号,阻塞住其它内存访问请求。
但这么做未免效率太低。这种影响总线的指令会随着核越来越多而变的越来越低效。可以想象,任意一个核上发起 lock 几乎会让所有的核都短暂的停止工作(除非完全不访问内存?)。我们今天只有两个或四个核,性能影响微乎其微。但是等到机器拥有 32,64 甚至更多核时,就可能相当严重了。
ps. 上面这个说法也不全然正确。因为既然内存锁在多线程编程中运用的非常广泛,自然在芯片设计上是要做优化的。在 Pentium Pro 以后,当被访问内存处于 cache 中时,lock# 信号不会被发到总线上,取而代之的是锁住 cache 。这样代价会小的多,但是在某些情况下依旧昂贵。(当多个核 cache 住同一块内存时会受影响)
轻量一点的方案是执行一条 CPUID 指令,它也可以保证前面的操作被串行化。到了Pentium III ,Intel 在 IA32 指令集中增加了 SFENCE 指令用来提供更细的控制粒度以更少的代价解决这个问题。在指令序列中插入 SFENCE 可以保证在此指令之前的写操作全部完成(非写操作的指令依旧允许乱序执行)。这样我们在另一个核里读相同内存时,几乎不会出错。
在这里我用了“几乎”,是因为诸如访问单项链表,判断标志数据的编程逻辑,对内存的读操作都是上下文相关的。我们可以断言执行次序不会被乱序执行影响。构造一个可能因为乱序读内存而有出错隐患的合适例子不太容易。但是从 Pentium 4 开始,严格上讲,我们需要用 LFENCE 指令(Pentium 3 没有提供也不需要这条指令)配合使用。它可以保证在此之前的 program ordering 上的读内存操作已经完成(否则逻辑结果可能因为多核间同步 cache 等原因而受到影响)。另外有 MFENCE 指令粒度粗一点,可以同时保证读写内存操作都已完成。
本来不打算翻资料,凭印象写的。写完这篇 blog 审了一遍,还是担心留下重大错误。就又一次翻阅了 2005 年在 Intel 网站上免费索取的 IA-32 Intel Architecture Software Developer's Manual Volume 3 的 Chapter 7 :Multiple-processor management 。
核对后,我想大概意思应该写清楚了,如果有小 bug 还请行家多多包涵。真有兴趣把这点东西搞清楚的朋友,莫信我的一家之言,查 Intel 的手册吧,它写的更加清楚和权威。btw ,不要问我该去哪找,去问 google 。
Comments
Posted by: 邓安良 | (53) May 22, 2013 10:18 AM
Posted by: donghao | (52) December 20, 2010 10:09 AM
Posted by: ctemple | (51) January 22, 2010 10:41 AM
Posted by: coder | (50) June 18, 2009 02:57 PM
Posted by: rockcarry | (49) December 25, 2007 05:43 PM
Posted by: Cloud | (48) December 18, 2007 04:24 PM
Posted by: m | (47) December 18, 2007 03:23 PM
Posted by: Cloud | (46) December 15, 2007 05:42 PM
Posted by: Cloud | (45) December 15, 2007 05:21 PM
Posted by: Anonymous | (44) December 15, 2007 04:25 PM
Posted by: m | (43) December 14, 2007 05:06 PM
Posted by: Siney | (42) December 13, 2007 09:49 PM
Posted by: Cloud | (41) December 13, 2007 12:44 AM
Posted by: 江峰 | (40) December 12, 2007 11:51 PM
Posted by: Cloud | (39) December 12, 2007 12:52 PM
Posted by: 江峰 | (38) December 12, 2007 11:11 AM
Posted by: Cloud | (37) December 10, 2007 05:01 PM
Posted by: Anonymous | (36) December 10, 2007 04:32 PM
Posted by: Cloud | (35) December 10, 2007 02:12 PM
Posted by: Bit Cowboy | (34) December 10, 2007 11:46 AM
Posted by: Cloud | (33) December 10, 2007 12:32 AM
Posted by: Ninstein | (32) December 9, 2007 11:12 PM
Posted by: Ninstein | (31) December 9, 2007 11:10 PM
Posted by: Cloud | (30) December 9, 2007 06:47 PM
Posted by: Ninstein | (29) December 9, 2007 03:22 PM
Posted by: Anonymous | (28) December 7, 2007 04:24 PM
Posted by: Anonymous | (27) December 7, 2007 04:19 PM
Posted by: Cloud | (26) December 7, 2007 02:42 PM
Posted by: rockcarry | (25) December 7, 2007 10:08 AM
Posted by: Anonymous | (24) December 7, 2007 09:50 AM
Posted by: 张英刚 | (23) December 7, 2007 09:10 AM
Posted by: Bit Cowboy | (22) December 7, 2007 09:06 AM
Posted by: Cloud | (21) December 6, 2007 08:15 PM
Posted by: Bit Cowboy | (20) December 6, 2007 07:12 PM
Posted by: rockcarry | (19) December 6, 2007 06:20 PM
Posted by: Rome | (18) December 6, 2007 05:52 PM
Posted by: Anonymous | (17) December 6, 2007 05:34 PM
Posted by: rockcarry | (16) December 6, 2007 05:21 PM
Posted by: Cloud | (15) December 6, 2007 05:01 PM
Posted by: rockcarry | (14) December 6, 2007 03:03 PM
Posted by: rockcarry | (13) December 6, 2007 02:51 PM
Posted by: Cloud | (12) December 6, 2007 02:51 PM
Posted by: rockcarry | (11) December 6, 2007 02:30 PM
Posted by: Anonymous | (10) December 6, 2007 01:21 PM
Posted by: rockcarry | (9) December 6, 2007 12:44 PM
Posted by: Cloud | (8) December 6, 2007 12:17 PM
Posted by: Cloud | (7) December 6, 2007 12:13 PM
Posted by: error.d | (6) December 6, 2007 11:45 AM
Posted by: Anonymous | (5) December 6, 2007 11:31 AM
Posted by: 江峰 | (4) December 6, 2007 11:06 AM
Posted by: 知名不具 | (3) December 6, 2007 10:22 AM
Posted by: 周伟明 | (2) December 6, 2007 09:03 AM
Posted by: WJY | (1) December 6, 2007 08:47 AM