X86平台乱序执行简要分析(翻译为主)
多处理器使用松散的内存模型可能会非常混乱,写操作可能会无序,读操作可能会返回不是我们想要的值,为了解决这些问题,我们需要使用内存栅栏(memory fences),或者说内存屏障(memory barrier)。
X86平台可能还算是使用松散内存模型的多处理器中还算比较好的了,它针对内存顺序有一定的规范要求,原文如下:
Loads are not reordered with other loads.
Stores are not reordered with other stores.
Stores are not reordered with older loads.
In a multiprocessor system, memory ordering obeys causality (memory ordering respects transitive visibility).
In a multiprocessor system, stores to the same location have a total order.
In a multiprocessor system, locked instructions have a total order.
Loads and stores are not reordered with locked instructions.
其中Load是载入的意思,Store是存储的意思,我们可以简单的对应read和write。这些规则主要的意思就是,Load操作不会和其他Load操作进行操作重排,Store操作不会和其他Store操作进行重排,Store操作不会和之前的Load操作进行重排,使用锁指令的时候不会进行操作重排,其中有些意思我也不是理解的非常透彻,大家自己理解。
同时X86平台也提供了内存屏障的操作指令,例如mfence,lfence和sfence。但是使用内存屏障指令会导致执行效率的下降,因为CPU需要循环等待。
这里有个重要的问题,你既不希望编译器针对你的代码加上栅栏导致运行效率过低,同时也不希望它给你生成不正确的代码。
在X86平台上,有一条重要的事项没有保障,就是“Load操作可能在不同的位置上面比它之前的Store操作更早的执行”,这里的位置是内存位置,比如我们简单的认为是一个变量。
有一个例子可以说明这个问题,比如x和y是在共享内存中的,他们被初始化为0,r1,r2是处理器的2个寄存器。
Thread 0 | Thread 1 |
mov [_x], 1 | mov [_y], 1 |
mov r1, [_y] | mov r2, [_x] |
当Thread0, 和1这两个线程同时在不同的CPU上被执行,就可能会发生r1, r2的值为0的情况。注意发生这种情况的原因就是因为CPU在Store操作之前先执行了Load操作。
发生这种事情会有什么影响呢?那我们举一个多线程锁的特殊例子,Peterson lock,它是一个针对2个线程互斥的特殊算法。它假设2个线程都有自己的线程ID,不是0,就是1,也就是说其中一个线程ID为0,一个为1.他的实现代码是这样的。
class Peterson
{
private:
// indexed by thread ID, 0 or 1
bool _interested[];
// who's yielding priority?
int _victim;
public:
Peterson()
{
_victim = ;
_interested[] = false;
_interested[] = false;
}
void lock()
{
// threadID is thread local,
// initialized either to zero or one
int me = threadID;
int he = - me;
_interested[me] = true;
_victim = me;
while (_interested[he] && _victim == me)
continue;
}
void unlock()
{
int me = threadID;
_interested[me] = false;
}
}
为了解释这个算法是如何工作的,让我们假设一下,我们就是其中的一个线程。当我(就是那个线程)需要获得锁的时候,我先将我感兴趣的_interested slot置为true,只有我能写这个slot,其他的线程只能读。同时我将_victim(牺牲者)置为我自己的ID。然后我检查另外的一个线程是否已经获得了锁,如果另外的线程已经获取到了锁,那么我就Spin,也就是循环等待。但是一旦另外的线程释放了锁,并且将它自己设置为_victim,那我就可以获取到锁了,然后就可以针对临界区的数据进行操作了。
如果已经基本理解了这个算法的意思,我们将问题简化一下,只抽取其中最关键部分的代码,将_interested数组中的2个slot,使用2个变量zeroWants和oneWants来替代。
他们初始化为:
zeroWants = false;
oneWants = false;
victim = ;
考虑2个线程同时操作时,我们列出下面的表格:
Thread 0 | Thread 1 |
zeroWants = true; victim = 0; while (oneWants && victim == 0) continue; // critical code zeroWants = false; |
oneWants = true; victim = 1; while (zeroWants && victim == 1) continue; // critical code oneWants = false; |
最后,我们再将上面的表格转换成伪汇编代码
Thread 0 | Thread 1 |
store(zeroWants, 1) store(victim, 0) r0 = load(oneWants) r1 = load(victim) |
store(oneWants, 1) store(victim, 1) r0 = load(zeroWants) r1 = load(victim) |
先我们来看下Load和Store zeroWants和oneWants2个变量,在上面的表格是我们期待的正确Load和Store顺序。但是处理器是允许先Load oneWants,然后再Store zeroWants的,同样也允许先Load zeroWants,然后再Store oneWants的。那么乱序执行的样子就可能就是下表这个样子:
Thread 0 | Thread 1 |
r0 = load(oneWants) store(zeroWants, 1) store(victim, 0) r1 = load(victim) |
r0 = load(zeroWants) store(oneWants, 1) store(victim, 1) r1 = load(victim) |
如果这样执行的话,由于zeroWants和oneWants初始化为0,那么r0, r1也就会变成0,那么spin loop(循环等待)就永远不会被执行,2个线程同时可以针对临界区的数据进行操作! Peterson lock 在X86平台上就失效了!
当然是有办法解决这个问题的,那么就是使用fence类的指令来强制按照正确的顺序执行。在同一个线程中,fence指令可以加在任何store zeroWants和load oneWants之间,在另外一个线程中情况也是一样。这样就能保证正确的执行顺序了。
当然我们在平时的编程过程中,并不需要过多的考虑乱序执行以及内存屏障,我们的操作系统比如linux,在它实现加锁的时候,已经将内存屏障的指令加入进去了。
X86平台乱序执行简要分析(翻译为主)的更多相关文章
- 【UEFI】---史上最全的X86平台启动流程分析(软硬结合)
最近研究了下X86处理器的启动流程分析,相比于常见的ARM来说,X86平台启动流程真的复杂了很多,本次基于项目实际的两个问题入手,研究了包括以下几个部分的内容: 1. 从硬件角度看启动流程 2. 从软 ...
- 简要分析武汉一起好P2P平台的核心功能
写作背景 加入武汉一起好,正式工作40天了,对公司的核心业务有了更多的了解,想梳理下自己对于P2P平台的认识. 武汉一起好,自己运营的yiqihao.com,是用PHP实现的,同时也帮助若干P2P平台 ...
- 英语学习/词典app行业top5简要分析
综述 根据豌豆夹上的下载量如下图所示,我们组确定的国内行业top5分别是:有道词典(黄明帅负责),金山词霸(黄珂锐负责),百度翻译(刘馨负责),百词斩(贾猛负责),英语流利说(亢建强负责)(时间有限, ...
- ITS简要分析流程(using Qiime)
Qiime安装 参考资料:http://blog.sina.com.cn/s/blog_83f77c940101h2rp.html Qiime script官方说明http://qiime.org/s ...
- Android Hal层简要分析
Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...
- RxJava && Agera 从源码简要分析基本调用流程(2)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...
- 面向英特尔® x86 平台的 Unity* 优化指南: 第 1 部分
原文地址 目录 工具 Unity 分析器 GPA 系统分析器 GPA 帧分析器 如要充分发挥 x86 平台的作用,您可以在项目中进行多种性能优化,以最大限度地提升性能. 在本指南中,我们将展示 Uni ...
- hidraw设备简要分析
关键词:hid.hidraw.usbhid.hidp等等. 下面首先介绍hidraw设备主要用途,然后简要分析hidraw设备驱动(但是不涉及到相关USB/Bluwtooth驱动),最后分析用户空间接 ...
- 基于X86平台的PC机通过网络发送一个int(32位)整数的字节顺序
1.字节顺序 字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端.大端两种字节顺序.小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处:大端字节序是高字节数据存 ...
随机推荐
- [转]C#中的Monitor类
object obj=new object(); Monitor在锁对象obj上会维持两个线程队列R和W以及一个引用T : (1) T是对当前获得了obj锁的线程的引用(设此线程为CurrThread ...
- POJ 1258 最小生成树
23333333333 完全是道水题.因为是偶自己读懂自己做出来的..T_T.prim的模板题水过. DESCRIPTION:John竞选的时候许诺会给村子连网.现在给你任意两个村子之间的距离.让你求 ...
- mysql sql 百万级数据库优化方案
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...
- GUID vs INT Debate【转】
http://blogs.msdn.com/b/sqlserverfaq/archive/2010/05/27/guid-vs-int-debate.aspx I recently read a bl ...
- 流控panabit的安装及配置
软件: 在panabit的下载页面上,没有最新的版本.刚开始就是从这个地方下载的,但是有一块网卡怎么也找不到.各种加载网卡驱动,最后失败. 之后,从其论坛中发现了最新的2013.05版本,将ISO刻盘 ...
- 去除Sql Server中回车换行符
这里使用了,sql 函数.replace(string_expression , string_pattern , string_replacement), 第一个参数:要查找的字段. 第二个参数:要 ...
- SQLite实现Top功能
SQlite本身没有top功能,无法向TSQL一样下Select top 100 * from tb_table,但SQLite提供了一个Limit关键字用来取得相应行数的资料 具体语法实例:Sele ...
- HDU5478 原根求解
看别人做的很简单我也不知道是怎么写出来的 自己拿到这道题的想法就是模为素数,那必然有原根r ,将a看做r^a , b看做r^b那么只要求出幂a,b就能得到所求值a,b 自己慢慢化简就会发现可以抵消n然 ...
- 如何在windows上搭建ftp服务器
FTP(File Transfer Protocol)是TCP/IP网络上两台计算机传送文件的协议,使得主机间可以共享文件.目前有很多软件都能实现这一功能,然而windows自带的IIS就可以帮助你搭 ...
- [网络技术][转]PPTP连接过程
转自:http://blog.csdn.net/zhu_hit/article/details/5698958 在未来几天会总结一下PPTP的工作过程,分为以下3篇讲述. 1. PPTP连接过程: 2 ...