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.字节顺序 字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端.大端两种字节顺序.小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处:大端字节序是高字节数据存 ...
随机推荐
- K需要修改的内容
1.需要保存默认案件,所有相关的页面的Title都要显示默认按键信息. 2.播放器需要调整,左侧的是播放信息,用户选择:案件/设备/然后就把该目录下的文件都展示出来.用户选择的时候马上进行播放.右侧有 ...
- PostgreSQL数据库系统优点
PostgreSQL 是世界上可以获得的最先进的开放源码的数据库系统, 它提供了多版本并行控制,支持几乎所有 SQL 构件(包括子查询,事务和用户定 义类型和函数), 并且可以获得非常广阔范围的(开发 ...
- Difference between Linearizability and Serializability
原文:http://stackoverflow.com/questions/8200015/what-is-the-difference-between-serializability-and-lin ...
- js事件应用
---恢复内容开始--- 一.自定义滚动条 var oDiv=document.getElementById('div1'); var oParent=document.getElementById( ...
- HDU 1828 扫描线(矩形周长并)
Picture Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Sub ...
- IBatis.net 输出SQL语句(七)
一.IBatis.net输出SQL语句到控制台 输出IBatis.net生成的SQL语句到控制台,能够方便调试. 如果要想输出IBatis.net的SQL语句到控制台,那么只需要做如下配置即可: &l ...
- encodeURI
encodeURI("http://www.cnblogs.com/season-huang/some other thing"); //整个URL进行编码"http:/ ...
- Android ViewPager自动播放
在开发Android应用的过程中,ViewPager有时候需要自动播放的功能,今天就介绍一下自动播放功能的实现,直接上代码: // viewpager auto play private static ...
- elementoryOS / ubuntu U盘启动问题的解决
具体现象: 进入U盘启动后,停顿在"start booting from usb device..."不动. 解决方法: 将syslinux文件夹下的syslinux.cfg中的 ...
- linux命令存放 bash: xxx command not found
参考资料:http://blog.sina.com.cn/s/blog_688077cf01013qrk.html 提示:bash: xxx command not found 首先就要考虑root ...