0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

编译器的乱序策略

dyquk4xk2p3d 来源:良许Linux 2023-05-19 14:46 次阅读

很多现代高级语言多提供了多线程并发技术,今天服务器CPU基本上都是多核架构,在Java中,JVM能够根据处理器特性(CPU多级缓存系统多核处理器等)适当对机器指令进行重排序,最大限度发挥机器性能。Java中的指令重排有两次,第一次发生在将字节码编译成机器码的阶段,第二次发生在CPU执行的时候,也会适当对指令进行重排。

写这篇文章的目的,是想明确下cpu指令乱序这件事。只要是熟悉计算机底层系统的同学就会知道,程序里面的每行代码的执行顺序,有可能会被编译器和cpu根据某种策略,给打乱掉,目的是为了性能的提升,让指令的执行能够尽可能的并行起来

知道指令的乱序策略很重要,原因是这样我们就能够通过barrier(内存屏障)等指令,在正确的位置告诉cpu或者是编译器,这里我可以接受乱序,那里我不能接受乱序等等。从而,能够在保证代码正确性的前提下,最大限度地发挥机器的性能。

10多年前的程序员对处理器乱序执行内存屏障应该是很熟悉的,但随着计算机技术突飞猛进的发展,我们离底层原理越来越远,这并不是一件坏事,但在有些情况下了解一些底层原理有助于我们更好的工作,比如现代高级语言多提供了多线程并发技术,如果不深入下来,那么有些由多线程造成问题就很难排查和理解

前言

这里我不打算讨论编译器的乱序策略,这里讨论的指令乱序,含义稍广些,包括在多核上分别执行的指令间,在时间维度上的乱序。

如果在多核cpu层面考虑乱序执行的话,我们要来梳理清楚以下几个概念:单核多核乱序执行顺序提交store bufferinvalid queue。最后会对x86arm/power架构的异同,做一个总结。

24a2495e-f5fc-11ed-90ce-dac502259ad0.jpg

单核 vs 多核

从多核的视角上来说,是存在着乱序的可能的。比如,假设存在变量x = 0,cpu0上执行写入W0(x, 1),对x写入1。接着在cpu1上,执行读取R1(x, 0),得到x = 0,这在x86和arm/power的cpu上都是可能出现的。原因是x86上cpu核和cache以及内存之间,存在着store buffer,当W0(x, 1)执行成功后,修改只存在于store buffer中,并未写到cache以及内存上,因此cpu1读取不到最新的x值。对于arm/power来说,同样也有store buffer,而且还可能会有invalid queue,导致cpu1读不到最新的x值。

对于没有invalid queue的x86系列cpu来说,当修改从store buffer刷入cache时,就能够保证在其他核上能够读到最新的修改。但是,对于存在invalid queue的cpu来说,则不一定。

为了能够保证多核之间的修改的可见性,我们在写程序的时候需要加上内存屏障,例如x86上的mfence指令。

乱序执行 vs 顺序提交

我们知道,在cpu中为了能够让指令的执行尽可能地并行起来,从而发明了流水线技术。但是如果两条指令的前后存在依赖关系,比如数据依赖,控制依赖等,此时后一条语句就必需等到前一条指令完成后,才能开始。

cpu为了提高流水线的运行效率,会做出比如:

1)对无依赖的前后指令做适当的乱序和调度;

2)对控制依赖的指令做分支预测;

3)对读取内存等的耗时操作,做提前预读;

等等。以上总总,都会导致指令乱序的可能。

但是对于x86的cpu来说,在单核视角上,其实它做出了Sequential consistency[1]的一致性保障。Sequential consistency的在wiki上的定义如下:

"... the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program."

也就是说,要满足Sequential consistency,必需保障每个处理器的指令执行顺序必需和程序给出的顺序一致。奇怪吧?这不就和我刚才说的指令乱序优化矛盾了嘛?其实并不矛盾,指令在cpu核内部确实是乱序执行和调度的,但是它们对外表现却是顺序提交的。

如果把ISA寄存器(如EAX,EBX等)和store buffer,作为cpu对外的接口的话,cpu只需要把内部真实的物理寄存器按照指令的执行顺序,顺序映射到ISA寄存器上,也就是cpu只要将结果顺序地提交到ISA寄存器,就可以保证Sequential consistency。

当然,以上是对x86架构的cpu来说的,ARM/Power架构的cpu在单核上的一致性保证要弱一些,无需保证Sequential consistency,因此也不需要顺序提交,只需保证控制依赖,数据依赖,地址依赖等指令的顺序即可。要想在这些弱一致性模型cpu下保证无关指令间的提交顺序,需要使用barrier指令。

Store Buffer & Invalid Queue

store buffer存在于cpu核与cache之间,对于x86架构来说,store buffer是FIFO,因此不会存在乱序,写入顺序就是刷入cache的顺序。但是对于ARM/Power架构来说,store buffer并未保证FIFO,因此先写入store buffer的数据,是有可能比后写入store buffer的数据晚刷入cache的。从这点上来说,store buffer的存在会让ARM/Power架构出现乱序的可能。store barrier存在的意义就是将store buffer中的数据,刷入cache。

在某些cpu中,存在invalid queue。invalid queue用于缓存cache line的失效消息,也就是说,当cpu0写入W0(x, 1),并从store buffer将修改刷入cache,此时cpu1读取R1(x, 0)仍是允许的。因为使cache line失效的消息被缓冲在了invalid queue中,还未被应用到cache line上。这也是一种会使得指令乱序的可能。load barrier存在的意义就是将invalid queue缓冲刷新。

X86 vs ARM/Power

对于x86架构的cpu来说,在单核上来看,其保证了Sequential consistency,因此对于开发者,我们可以完全不用担心单核上的乱序优化会给我们的程序带来正确性问题。在多核上来看,其保证了x86-tso模型,使用mfence就可以将store buffer中的数据,写入到cache中。而且,由于x86架构下,store buffer是FIFO的和不存在invalid queue,mfence能够保证多核间的数据可见性,以及顺序性。[2]

对于arm和power架构的cpu来说,编程就变得危险多了。除了存在数据依赖,控制依赖以及地址依赖等的前后指令不能被乱序之外,其余指令间都有可能存在乱序。而且,它们的store buffer并不是FIFO的,而且还可能存在invalid queue,这些也同样让并发编程变得困难重重。因此需要引入不同类型的barrier来完成不同的需求。[3]

总结

从上面的介绍可以知道,开发者想要做好并发编程是多么困难的事情,但是我们至少跨出了第一步,也就是定义困难本身。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    18288

    浏览量

    222193
  • 计算机
    +关注

    关注

    19

    文章

    6651

    浏览量

    84562
  • 编译器
    +关注

    关注

    1

    文章

    1577

    浏览量

    48625

原文标题:当我们在谈论 cpu 指令乱序的时候,究竟在谈论什么?

文章出处:【微信号:良许Linux,微信公众号:良许Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    几款C语言编译器推荐

    一些刚开始接触C语言编译的网友想下载一款C语言编译器来使用,不过,网络上有不少C语言编译器相关的软件,让人很难抉择。
    发表于 09-05 09:19 9385次阅读

    ICC AVR编译器的安装与使用

    ICCAVR编译器的安装、运行、破解、使用 用ICCAVR编译器产生初始化程序和程序框架
    发表于 07-09 18:06 258次下载

    基于CoSy的编译器开发的研究

    CoSy是ACE公司开发的编译器构造框架[1]。它提供共享工具和引擎来构造编译器编译器开发者只专注于目标机相关代码的开发。CoSy框架生成的编译器具有可扩展性和可移植性。可以根据目
    发表于 08-19 17:49 0次下载
    基于CoSy的<b class='flag-5'>编译器</b>开发的研究

    stm8编译器下载

    stm8编译器下载
    发表于 04-27 17:46 4次下载

    PICC编译器下载

    PICC编译器下载
    发表于 05-25 17:44 168次下载

    编译器是如何工作的_编译器的工作过程详解

    随着计算机的发展,编译器已经发挥着十分重要的作用。本文主要介绍了编译器的种类、编译器的工作原理以及编译器工作的具体操作过程及步骤详解。
    发表于 12-19 12:54 1.5w次阅读

    既然C编译器是C语言写,那么第一个C编译器是怎样来的?

    既然C编译器是C语言写的,那第一个C编译器是怎样来的?
    的头像 发表于 02-25 15:47 2803次阅读

    王垠谈编译器

    由于早期的 Lisp 编译器生成的代码效率普遍低下,成为了 Lisp 失败的主要原因之一。而现在的高性能 Lisp 编译器(比
    的头像 发表于 03-30 10:45 1871次阅读

    交叉编译器安装教程

    交叉编译器中“交叉”的意思就是在一个架构上编译另外一个架构的代码,相当于两种架构“交叉”起来了。Ubuntu 自带的 gcc 编译器是针对 X86 架构的,而我们现在要编译的是 ARM
    的头像 发表于 09-29 09:12 2659次阅读

    当我们在谈论cpu指令乱序的时候,究竟在谈论什么?

    知道指令的乱序策略很重要,原因是这样我们就能够通过barrier(内存屏障)等指令,在正确的位置告诉cpu或者是编译器,这里我可以接受乱序,那里我不能接受
    的头像 发表于 05-19 14:42 888次阅读
    当我们在谈论cpu指令<b class='flag-5'>乱序</b>的时候,究竟在谈论什么?