引言
在同一个线程中,如果指令之间没有前后的依赖关系,那么这些指令则在可能会会乱序执行。当时指令之间存在依赖关系,比如读写时,我们需要重点关注,因为这些指令如果乱序就会对代码逻辑产生可见的影响。
- 比如下的代码可能以任何数据执行,他们的执行顺序无法预测,因为他们之间没有依赖
| int x = 1; |
| int y = 2; |
| int z = 3; |
- 但是下面的代码将被串行执行,因为他们y的赋值依赖x的值,z 的值依赖 x 和 y
| int x = fun1(); |
| int y = fun2(x); |
| int z = x + y; |
乱序的原因
产生指令执行顺序被打乱的原因有很多,比如编译器的优化、cpu 指令流水线的优化、cpu 数据核内写缓存cache 一致性限制、多个cpu间网络数据传递等。总之乱序的原因是为了提高程序执行的效率。这了我们要有一个概念 假设cpu 执行一条指令需要一个时钟周期,那么cpu 写L1存储也是一个周期,写L2大概10个周期,写内存大概需要100个周期,因此为了解决各个系统层级之间的读写速度差,cpu会出现大量的优化措施,这些就会导致指令的乱序执行。
编译器
编译优化手段有很多。比如常量折叠,无用代码消除等。
比如下面的例子
| int main() { |
| int x = 1; |
| int y = 2; |
| int z = 3; |
| int a = y + z; |
| int b = a + 1; |
| return b; |
| } |
如果采用 -O0 编译 gcc -g -c -O0 ./a.c && objdump -d -M intel -S ./a.o
生成汇编如下
| int main() { |
| 0: f3 0f 1e fa endbr64 |
| 4: 55 push rbp |
| 5: 48 89 e5 mov rbp,rsp |
| int x = 1; |
| 8: c7 45 ec 01 00 00 00 mov DWORD PTR [rbp-0x14],0x1 |
| int y = 2; |
| f: c7 45 f0 02 00 00 00 mov DWORD PTR [rbp-0x10],0x2 |
| int z = 3; |
| 16: c7 45 f4 03 00 00 00 mov DWORD PTR [rbp-0xc],0x3 |
| int a = y + z; |
| 1d: 8b 55 f0 mov edx,DWORD PTR [rbp-0x10] |
| 20: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] |
| 23: 01 d0 add eax,edx |
| 25: 89 45 f8 mov DWORD PTR [rbp-0x8],eax |
| int b = a + 1; |
| 28: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] |
| 2b: 83 c0 01 add eax,0x1 |
| 2e: 89 45 fc mov DWORD PTR [rbp-0x4],eax |
| return b; |
| 31: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] |
| } |
| 34: 5d pop rbp |
| 35: c3 ret |
但是如果采用-O2编译 gcc -g -c -O2 ./a.c && objdump -d -M intel -S ./a.o
则会生成如下汇编
| int main() { |
| 0: f3 0f 1e fa endbr64 |
| int y = 2; |
| int z = 3; |
| int a = y + z; |
| int b = a + 1; |
| return b; |
| } |
| 4: b8 06 00 00 00 mov eax,0x6 |
| 9: c3 ret |
这个代码等价于
| int main() { |
| 0: f3 0f 1e fa endbr64 |
| return 6; |
| } |
| 4: b8 06 00 00 00 mov eax,0x6 |
| 9: c3 ret |
可以看到不但代码顺序是可以打乱的,甚至连无用代码都可删除,常量可以在编译期计算。
编译优化有个前提,就是假设程序是单线程执行的,果编译器希望对程序指令的执行顺序做出改变,只要这些改变不影响该程序在单线程情况下的运行结果,那么这些改变就是允许的。
比如下面的代码 第6行和第7行的执行顺序就是无法保证的
| |
| int main() { |
| int a = 10, b = 20, c = 30, d = 40, e = 50, f = 60, g = 70, h = 80; |
| int x = a + b + c + d; |
| int y = e + f + g + h; |
| std::cout |