CPU原理之指令重排与内存屏障

Posted by WGrape的博客 on October 3, 2022

文章内容更新请以 WGrape GitHub博客 : CPU原理之指令重排与内存屏障 为准

前言

本文原创,著作权归WGrape所有,未经授权,严禁转载

一、指令重排

指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。指令重排一般分为指令级别重排和编译器级别重排。

1、顺序执行

在早期的处理器中,处理器执行指令的顺序就是内存中汇编指令的顺序。

image

2、乱序执行

为了提高处理器的执行效率,在CPU等资源空闲的时候,会再去尽可能执行其他的无前后逻辑的指令。这就是指令重排后的乱序执行。

image

3、as-if-serial 原则

as-if-serial 译为 好像是顺序的,表示指令重排的原则是不能改变指令的执行结果,结果必须和指令顺序执行时的结果一样。这是编译器和CPU都必须遵守的原则,当然无法100%保障,由此引出 内存屏障

二、内存屏障 Memory barrier

处理器的指令重排是基于分析机制,这种机制无法保证100%的正确分析,因此指令重排有时会导致多线程程序产生各种各样的意外。

由于指令重排机制,因此有必要再提供一种机制来消除乱序执行带来的坏影响,也就是说应该允许程序员显式的告诉处理器对某些地方禁止乱序执行,这种机制就是所谓内存屏障,内存屏障是一类同步屏障指令。

不同架构的处理器在其指令集中提供了不同的指令来发起内存屏障,对应在编程语言当中就是提供特殊的关键字来调用处理器相关的指令。

1、指令重排规则

Y表示前后两个操作允许重排,N则表示不允许重排,与这些规则对应是的禁止重排的内存屏障。

image

所谓的数据依赖性就是如果两个操作访问同一个变量,且这两个操作中有一个是写操作,则称这两个操作存在数据依赖性。举个简单例子:

a = 50; //write
b = a; //read

a = 100; //write
a = 150; //write

a = b; //read
b = 200; //write

处理器和编译都会遵循数据依赖性,不会改变存在数据依赖关系的两个操作的顺序。

2、内存屏障指令

大多数处理器提供了内存屏障指令:

  • 完全内存屏障(full memory barrier)保障了早于屏障的内存读写操作的结果提交到内存之后,再执行晚于屏障的读写操作。
  • 内存读屏障(read memory barrier)仅确保了内存读操作;
  • 内存写屏障(write memory barrier)仅保证了内存写操作。

内存屏障是底层原语,在不同体系结构下变化很大而不适合推广,具体使用需要认真研读硬件的手册以确定内存屏障的办法。其中x86指令集中的内存屏障指令是:

lfence (asm), void _mm_lfence (void) 读操作屏障
sfence (asm), void _mm_sfence (void)[1] 写操作屏障
mfence (asm), void _mm_mfence (void)[2] 读写操作屏障

不仅CPU,存储器也提供了另一套语义的内存屏障指令,可以参考这里