任务队列、CPU Load、指令乱序、指令屏障
CPU性能优化手段 - 缓存
为了提高程序的运行性能, 现代CPU在很多方面对程序进行了优化
例如: CPU高速缓存, 尽可能的避免处理器访问主内存的时间开销, 处理器大多会利用缓存以提高性能
多级缓存
L1 Cache (一级缓存)是CPU第一层高速缓存, 分为数据缓存和指令缓存, 一般服务器CPU的L1缓存的容量通常在32-4096kb
L2 Cache (二级缓存) 由于L1高速缓存的容量限制, 为了再次提高CPU的运算速度, 在CPU外部放置一高速缓存存储器, 即二级缓存
L3 Cache(三级缓存)现在都是内置的, 而它的实际作用既是, L3缓存的应用可以进一步降低内存延迟, 同时提升大数据量计算时处理器的性能. 具有较大L3缓存的处理器更有效的文件系统缓存行为及较短消息和处理器队列长度. 一般是多核共享一个L3缓存
CPU在读取数据时, 先在L1中寻找, 再从L2中寻找, 再从L3中寻找, 然后是内存, 最后是外存储器
缓存同步协议
多CPU读取同样的数据进行缓存, 进行不同运算之后, 最终写入主内存以那个CPU为准? 在这种高速缓存回写的场景下, 有一个缓存一致性协议, 多数CPU厂商对它进行了实现.
即MESI协议, 它规定每条缓存有个状态位, 同时定义了下面四种状态:
- 修改态(Modified) 此cache行已被修改过(脏行), 内容已不同于主内存, 为此cache专有
- 专有态(Exclusive) 此cache行同于主存, 但它不出现于其他cache中
- 共享态(Shared) 此cache行同于主存, 但也出现于其他cache中
- 无效态(Invalid) 此cache行无效(空行)
多处理时, 单个CPU对缓存中的数据进行了改动, 需要通知给其他CPU, 也就意味着, CPU处理要控制自己的读写操作, 还要监听其他CPU发出的通知, 从而保证最终一致
CPU性能优化手段 - 运行时指令重排
指令重排的场景: 当CPU写缓存时发现缓存区块正被其它CPU占用, 为了提高CPU处理性能, 可能将后面的读缓存命令优先执行.
当然也并非随便重排, 需要遵循as-if-serial语义
as-if-serial语义的意思指: 不管怎么重排序, 程序的执行结果不能被改变
编译器, runtime和处理器都必须遵守as-if-serial语义, 也就是说, 编译器和处理器不会对存在数据依赖关系的操作做重排序
两个问题
-
CPU高速缓存下有一个问题:
缓存中的数据与主内存的数据并不是实时同步的, 各CPU间缓存的数据也不是实时同步. 在同一时间点, 各CPU所看到的同一内存地址的数据的值可能是不一致的. -
CPU执行指令重排序优化的一个问题:
虽然遵守了as-if-serial语义, 但仅在单CPU自己执行的情况下能保证结果正确. 多核多线程中, 指令逻辑无法分辨因果关联, 可能出现乱序执行, 导致程序运行结果错误
解决方法 - 内存屏障
处理器提供了两个内存屏障指令(Memory Barrier)用于解决上述两个问题:
写内存屏障(Store Memory Barrier): 在指令后插入Store Barrier, 能让写入缓存中的最新数据更新写入主内存, 让其他线程可见
强制写入主内存, 这种显示调用, CPU就不会因为性能考虑而进行指令重排
读内存屏障(Load Memory Barrier): 在指令前插入Load Barrier, 可以让高速缓存中的数据失效, 强制从新从主内存读取数据
强制读取主内存内容, 让CPU缓存和主内存保持一致, 避免了缓存导致的一致性问题
一、CPU Load
cpu load是对使用或者等待cpu进程的统计(数量的累加):每一个使用(running)或者等待(runnable)CPU的进程,都会使load值+1;每一个结束的进程,都会使load值-1。
大部分Unix操作系统只计算running和runnable的进程。但是Linux系统除了上述两种状态进行,还计算uninterruptible sleep状态的进程(通常是在等待磁盘IO)。
cpu load average:分别表示1分钟、5分钟和15分钟内的cpu负载均值,使用命令:w,uptime,top。
二、工作队列(workqueue)是Linux内核中把工作延迟执行的一种手段,工作队列主要目的是节省资源,不创建线程,没有CPU和内存开销,其比较适合很微小的任务,比如执行某个唤醒工作等。
三、指令乱序
通过改变原有执行顺序而减少时间的执行过程称之为乱序执行,也称为重排,在单核情况下,乱序技术提高了运算速度。
(1)处理器乱序分类
现代处理器采用指令并行技术,在不存在数据依赖性的前提下,处理器可以改变语句对应的机器指令的执行顺序来提高处理器执行速度;
现代处理器采用内部缓存技术,导致数据的变化不能及时反映在主存所带来的乱序;
现代编译器为优化而重新安排语句的执行顺序;
(2)as-if-serial语义
无论是处理器还是编译器,不管怎么重排都要保证(单线程)程序的执行结果不能被改变,这就是as-if-serial语义.比如烧水煮茶的最终结果永远是煮茶,而不能变成烧水.为了遵循这种语义,处理器和编译器不能对存在数据依赖性的操作进行重排,因为这种重排会改变操作结果
四、内存屏障
对于多核情况,其中的某些”自作聪明”的乱序优化导致多线程程序产生各种各样的意外.因此有必要存在一种机制来消除乱序执行带来的坏影响,也就是说应该允许程序员显式的告诉处理器对某些地方禁止乱序执行.这种机制就是所谓内存屏障.
不同架构的处理器在其指令集中提供了不同的指令来发起内存屏障,对应在编程语言当中就是提供特殊的关键字(比如:volatile)来调用处理器相关的指令.
内存屏障的分类:
Store就是将处理器缓存中的数据刷新到内存中,而Load则是从内存拷贝数据到缓存当中.
StoreLoad Barriers同时具备其他三个屏障的效果,因此也称之为全能屏障,是目前大多数处理器所支持的,但是相对其他屏障,该屏障的开销相对昂贵.在x86架构的处理器的指令集中,lock指令可以触发StoreLoad Barriers.