首页 > 其他分享 >指令重排序

指令重排序

时间:2024-07-21 15:27:36浏览次数:17  
标签:周期 并行 指令 时延 寄存器 排序 时钟

CPU内部结构

图例

在这里插入图片描述

CPU由多个功能部件构成
在这个结构中,一条指令执行时,要依次用到多个功能部件,分成多个阶段,虽然每条指令是顺序执行的,但每个部件的工作完成以后,就可以服务于下一条指令,从而达到并行的效果

这种结构叫做流水线(pipeline)结构

流水线(pipeline)结构

比如典型的RISC指令在执行过程会分成前后共5个阶段

  • IF: 获取指令
  • ID(或RF): 指令解码和获取寄存器的值
  • EX: 执行指令
  • ME(或MEM): 内存访问(如果指令不涉及内存访问,这个阶段可以省略)
  • WB: 写回寄存器

Intel 的 Ice Lake 架构的CPU下的执行单元

  • 在这里插入图片描述

  • 其他执行单元还有: BM、Vec ALU 、Vec SHIF、Vec Add、Vec Mul、Shuffle

因为CPU内部存在着多个功能单元,所以在同一时刻,不同的功能单元其实可以服务于不同的指令

  • 在这里插入图片描述

这样的话,多条指令实质上是并行执行的,从而减少了总的执行时间,这种并行叫做指令级并行

  • 在这里插入图片描述

如果没有这种并行结构,或者由于指令之间存在依赖关系,无法并行,那么执行周期就会大大加长

  • 在这里插入图片描述

案例

假设load和store指令需要3个时钟周期来读写数据,add指令需要1个时钟周期,mul指令需要2个时钟周期

图中橙色的编号是原来的指令顺序,绿色的数字是每条指令开始的时钟周期,你把每条指令的时钟周期累计一下就能算出来

最后一条指令开始的时钟周期是20,该条指令运行需要3个时钟周期,所以在第22个时钟周期执行完所有指令
在这里插入图片描述

右边是重新排序后的指令,一共花了13个时钟周期

  • 仔细看一下左边前两条指令,这两条指令的意思是:先加载数据到寄存器,然后再做一个加法
  • 但加载需要3个时钟周期,所有add指令无法执行,只能干等着
  • 右列的前3条都是load指令,它们之间没有数据依赖关系,可以每个时钟周期启动一个,到了第四个时钟周期,每一条指令的数据已经加载完毕,所以就可以执行加法运算了

在这里插入图片描述

  • 可以把右边的内容画成上图所示,很多指令在时钟周期上是重叠的,这就是指令级并行的特点
  • 当然,不是所有指令都可以并行,最后的3条指令就是顺序执行

导致无法并行的原因

数据依赖约束

如果后一条指令要用到前一条指令的结果,那必须排在它的后面,比如下面两条指令: add 和 mul

对于第2条指令来说,除了获取指令的阶段(IF)可以和第一条指令并行以外,其他阶段需要等第一条指令的结果写入r1,第2条指令才可以使用r1的值继续运行

add r2, r1
mul r3, r1

在这里插入图片描述

功能部件约束

如果只有一个乘法计算器,那么一次只能执行一条乘法运算

在这里插入图片描述

指令流出约束

指令流出部件一次流出n条指令

寄存器约束

寄存器数量有限,指令并行时使用的寄存器不可以超标

后者可以合并成为一类,称为资源约束

其他无法并行的原因

在数据依赖约束中,如果有因为使用同一存储位置,而导致不能并行,可以用重命名变量方式消除,这类约束被叫做伪约束

而先写后写,以及先读后写的伪约束的两种呈现方式

先写后写:
  • 如果指令A写一个寄存器或内存位置,B也写一个同一个位置,就不能改变A和B的执行顺序
  • 不过可以修改程序,让A和B写不同的位置
先读后写:
  • 如果A必须在B写某个位置之前的读某个位置,那么不能改变A和B的执行顺序
  • 除非能够通过重命名让它们使用不同的位置

数据的依赖图(dependence graph)

前提

在这里插入图片描述

它的边代表了值的流动,比如a行加载了一个数据到r1,b行利用r1来做计算

所以b行依赖a行,这个图叫优先图(precedence graph),因为a比b优先,b比d优先

可以给图中的每个节点再加上两个属性,利用这两个属性,就可以对指令进行排序了

  • (1) 操作类型,因为这涉及它所需要的功能单元
  • (2) 时延属性,也就是每条指令所需的时钟周期

图中的a、c、e、g是叶子,它们没有依赖任何其他节点,所以尽量排在前面

b、d、f、h必须出现在各自所依赖的节点后面,而根节点i,总是要排在最后面

累计时延

根据时延属性,计算出每个节点的累计时延(每个节点的累计时延等于父节点的累计时延加上本节点的时延)

其中 a-b-d-f-h-i路径是关键路径,代码执行的最少时间就是这条路径所花的时钟周期之和

在这里插入图片描述

因为a在关键路径上,所以首先考虑把a节点排在第1行

在这里插入图片描述

剩下的树中,c-d-f-h-i变成了关键路径,因为c的累计时延最大。c节点可以排在第2行

在这里插入图片描述

b和e的累计时延都是最长的,但由于b必须在a执行完毕后,才会开始执行,所以最好等够a的3个时钟周期,否则还是会空等,所以先不考虑b,而是把e放到第3行

在这里插入图片描述

继续按照这个方式排,最后可以获得 a-c-e-b-d-g-f-h-i的指令序列

不过这个代码其实还可以继续优化:也就是发现并消除其中的伪约束

消除伪约束

在这里插入图片描述

c 和 e都向r2里写了值,而d使用的是c写入的值

在这里插入图片描述

如果改变变量名称,比如让e使用r3寄存器,就可以去掉e跟d,以及e与c之间的伪约束,让e就可以排在c和d之前

同理,也可以让g使用r4寄存器,使得g可以排在e和f的前面

当然了,在这个示例中,这种改变并没有减少总的时间消耗,因为关键路径上的依赖没有改变,它们都使用了r1寄存器

但是在别的情况下,就有可能带来更大的优化

其实是采用了一种最常见的算法,List Scheduling 算法

  • (1) 把变量重命名消除伪约束
  • (2) 创建依赖图
  • (3) 为每行代码计算优先值(计算方法可以有很多,比如示例中的基于最长时延的方法就是一种)
  • (4) 迭代处理代码并排序

NP-完全是什么意思?

也就是这类问题找不到一个随规模(代码行数)计算量增长比较慢的算法(多项式时间算法)来找到最优解

反之,有可能计算量会随着代码行数呈指数级上升

当然,找最优解太难,可以退而求其次,找一个次优解

就比如我们用地图软件导航的时候,没必要要求导航路径每次都是找到最短的

标签:周期,并行,指令,时延,寄存器,排序,时钟
From: https://blog.csdn.net/weixin_40398522/article/details/140564670

相关文章

  • 常见的排序算法——堆排序(四)
    本文记述了针对堆排序同时实施减少数据交换和Floyd方法的一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。◆思想减少数据交换的操作,请参考堆排序(二);Floyd方法,请参考堆排序(三)(此处略去详细说明)。◆实现排序代码采用《算法(第4版)》的“排序算法类模板”实现。(......
  • 常见的排序算法——堆排序(三)
    本文记述了针对堆排序实施Floyd方法的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。◆思想“大多数在下沉排序期间重新插入堆的元素会被直接加入到堆底。Floyd在1964年观察发现,我们正好可以通过免去检查元素是否到达正确位置来节省时间。”(引......
  • 从 Pandas 到 Polars 二十三:如果你的数据已经排序,Polars可以为你提供助力
    Polars针对处理已排序的数据进行了优化。要访问这些优化,你需要使用set_sorted标志告诉Polars数据已经排序。set_sorted主要用于以下两种情况:标记单个或多个列已排序:当你知道DataFrame的某个或某些列是按升序或降序排列时,你可以使用set_sorted来标记这些列。这将告诉P......
  • 七大排序算法的Python实现
    七大排序算法的Python实现1.冒泡排序(BubbleSort)算法思想冒泡排序通过重复交换相邻的未按顺序排列的元素来排序数组。每次迭代都将最大的元素“冒泡”到数组的末尾。复杂度分析时间复杂度:O(n^2)空间复杂度:O(1)defbubble_sort(arr):n=len(arr)for......
  • 排序
    define_CRT_SECURE_NO_WARNINGS1include"mySort.h"voidmySort::printArr(vector&vec){for(constauto&i:vec){cout<<i<<"";}}voidmySort::BubbleSort(vector&vec){for(intj=vec.size()-1;j>=......
  • 各种快速排序-史诗级巨作
    定义快速排序(英语:Quicksort),又称分区交换排序(英语:partition-exchangesort),简称「快排」,是一种被广泛运用的排序算法。基本原理与实现过程快速排序的工作原理是通过分治的方式来将一个数组排序。快速排序分为三个过程:将数列划分为两部分(要求保证相对大小关系);递归到两个子序......
  • 嵌入式学习记录——C基础(数组与排序)
    数组与排序数组一维数组二维数组排序冒泡排序选择排序数组数组是由一个或者多个相同数据类型的数据组成的一个集合一维数组如果将数组看做一个坐标轴,一维数组则如同只有X坐标,每个数组中的元素内存地址都是连续的,当数据类型和首个元素a[0]确定时,后续a[i]依次递增......
  • Python中用来排序的方法sort、sorted
    sort与sorted区别:sort是应用在list上的方法,而sorted可以对所有可迭代的对象(他们可以是list、dict、set、甚至是字符串)进行排序操作。list的sort方法返回的是对已经存在的列表进行操作,无返回值,而内建函数sorted方法返回的是一个新的list,而不是在原来的基础上进行......
  • 常见的排序算法——堆排序(二)
    本文记述了针对堆排序微小改动的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。◆思想堆的下沉操作中用到了昂贵的数据交换操作,此改动参考无交换的插入排序的思想,实现了减少数据交换的下沉操作。先将要下沉的元素存放在临时空间中,再将下降过程中遇......
  • 快速排序quicksort
    #include<iostream>usingnamespacestd;intpartition(inta[],intlow,inthigh){ intpivot=a[low]; while(low<high) { while(low<high&&a[high]>=pivot)//先从high开始 high--; a[low]=a[high]; while(low<high......