文章目录
-
- 建议阅读文档
- 乱序的定义
- 屏障的定义
- 硬件及软件技术的变化
- 优化带来的问题
- 阻止被优化的技术
-
-
- 编译器内存屏障
- 编译器内存屏障实验代码
- CPU 内存屏障
-
-
- RISCV的CPU内存屏障宏
- ARM的CPU内存屏障宏
- 各个指令集架构的内存屏障指令
建议阅读文档
- 内存访问顺序 - part1: 介绍
- 内存访问顺序 - part2: 屏障及Linux kernel中屏障的使用
- 内存访问顺序 - part3: ARM体系架构中的内存访问顺序
乱序的定义
我们先不考虑乱序,先把顺序 定义一下
C程序顺序 : 可能会被优化,与 二进制程序不一样
二进制(汇编)程序顺序 : cpuA 读到的顺序
执行顺序 : cpuA 执行的顺序
感知顺序 : cpuB 感知到的 "CPUA执行的顺序" , 和 "CPUA执行的顺序" 不一定相同
这里的乱序 指的是 编译器乱序,观察者乱序
观察者乱序的种类包括
1. 执行者乱序(out of order execution) 导致的 观察者乱序
2. memory order 情景1 : 可以交换执行顺序的内存操作 由于交换 导致的 观察者乱序
3. memory order 情景2 : 没有交换执行顺序,但是执行结果顺序由于 cache miss 而交换 导致的观察者乱序
屏障的定义
屏障 指的是 编译器屏障,内存屏障(在这里用cpu内存屏障来代替)
cpu 内存屏障可以解决三个问题
1. 编译器优化导致的乱序
2. cpu运行优化导致的乱序
3. 弱 memory order 导致的问题
有人要说了,那 cpu 内存屏障 可以替代 编译器屏障 ,为什么 还要 编译器屏障呢?
因为 cpu 内存屏障 的代价 比 编译器屏障 大 ,会增加程序运行时间
- cpu内存屏障与 memory order 的关系
硬件及软件技术的变化
1. 编译器的优化
当优化开关打开时
编译器会真对不同的cpu 做一些优化
并且会优化掉编译器认为重复的操作
例如
情景1
我连续写了串口的发送寄存器两次,他会优化掉
我就是想写两次来打印两个字符
2. 运行时的优化
硬件一设计出来就有,无法关闭
为了 提高 " 流水线 " 性能
新式处理器可以采用 " 超标量 体系结构 “ 和 ” 乱序执行 " 技术 , 可以在 一个时钟周期 中 并行执行多条指令 ;
但是 CPU 执行优化会导致 指令乱序执行 , 后面的指令先于前面的指令执行 , 导致 寄存器中的值冲突 ;
例如
情景1 : out of order execution 导致的观察者乱序
我的程序先配置 串口速率寄存器,然后 向 串口发送寄存器 写值
cpu 发射的时候. 可能串口速率寄存器还没写值 , 但是 串口发送寄存器已经写了一个数了
情景2 : memory order 导致的观察者乱序
我的程序先配置 串口速率寄存器,然后 向 串口发送寄存器 写值
cpu 发射的时候. 可能串口速率寄存器配了值(效果还没出来,硬件还没处理好这个值), 串口发送寄存器马上就写了一个数.
优化带来的问题
1. 编译器优化
优化后,不是我想要的代码逻辑
不优化,指令太多,跑的时间太长
诉求 : 不想优化的地方你不许优化
2. 运行时优化
优化后,先后顺序会搞反(包括执行顺序,效果顺序)
不优化,没有充分利用硬件,跑的不够快 ??? 错了,没得选,必须得优化
诉求 : 某些顺序有要求的指令序列,你得按照顺序跑
阻止被优化的技术
编译器内存屏障
1. 编译器优化(-O2 才能打开,不打开默认是不优化.你写什么顺序,就是什么顺序.你写从内存中load,就不会优化到register)
用编译器的提示指令来处理
1. 交换指令顺序/合并指令
1.显式 #define barrier() asm volatile("": : :“memory”)
2.隐式 调用一个函数
3.显式 asm volatile(“yield” ::: “memory”);
4.显式 volatile
2. 编译器做寄存器的优化,而不再内存中load
1.显式 #define barrier() asm volatile("": : :“memory”)
2.隐式 调用一个任意函数
3.显式 asm volatile(“yield” ::: “memory”);
4.显式 READ_ONCE
5.显式 volatile
编译器内存屏障实验代码
https:
该仓库 做了 基于四个架构(arm32/arm64/rv32/rv64)的 编译器内存优化 编译器内存屏障 练习
优化等级涉及 7种等级
无Ox 等价于 -O0
-Og
-O 等价于-O1
-Os
-O2
-O3
-Ofast
屏障方法涉及 三种
1. 显式 #define barrier() asm volatile("": : :“memory”)
2. 隐式 调用一个函数
3. 显式 volatile
CPU 内存屏障
2. 运行时优化(一直存在)
用架构定义的提示指令来处理
注意 : 这个指令完全是架构定义的,所以不同的架构 实际调用的指令不同
注意 : 所有的CPU内存屏障封装都隐式包含了编译器屏障
可以用内嵌汇编的方式调用,但是linux中定义好了一些 cpu内存屏障宏,为什么不用呢?
smp_wmb smp_rmb smp_mb
RISCV的CPU内存屏障宏
#define nop() __asm__ __volatile__ ("nop")
#define RISCV_FENCE(p, s) \
__asm__ __volatile__ ("fence " #p "," #s : : : "memory")
#define mb() RISCV_FENCE(iorw,iorw)
#define rmb() RISCV_FENCE(ir,ir)
#define wmb() RISCV_FENCE(ow,ow)
#define __smp_mb() RISCV_FENCE(rw,rw)
#define __smp_rmb() RISCV_FENCE(r,r)
#define __smp_wmb() RISCV_FENCE(w,w)
ARM的CPU内存屏障宏
#define isb() __asm__ __volatile__ ("isb" : : : "memory")
#define dsb() __asm__ __volatile__ ("dsb" : : : "memory")
#define dmb() __asm__ __volatile__ ("dmb" : : : "memory")
#define mb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
#define rmb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
#define wmb() do { if (arch_is_coherent()) dmb(); else barrier(); } while (0)
各个指令集架构的内存屏障指令
riscv
fence sync thread
fence.i sync instr & data
sfence.vma virtual memory fence
armv8
dmb data memory barrier
dsb data sync barrier
isb instruction sync barrier
armv7
dmb data memory barrier
在DMB之后的显示的内存访问执行前,保证所有在DMB指令之前的内存访问完成。
dsb data sync barrier
等待所有在DSB指令之前的指令完成(之后再执行后续的指令,译注)。
isb instruction sync barrier
清除(flush)流水线,使得所有ISB之后执行的指令都是从cache或内存中获得的(而不是流水线中的)
armv6
dmb
dsb
PrefetchFlush
The PrefetchFlush instruction flushes the pipeline in the processor,
so that all instructions following the pipeline flush are fetched
from cache or memory after the instruction has been completed.