作者:Yaong
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
 
记录一下对 list-scheduling algorithm的学习过程。
 
指令调度的一般性目的是为了获得更快的程序执行速度。
指令调度器需要满足在执行结果相同的前提下,最小化程序块的执行时间。
 
指令调度受到三方面的约束,分别是数据的流向,单条指令的执行时间(delay),以及目标处理器的处理能力(多个并行的运算单元)。
数据的流向由DAG图表示。
 
指令调度需要满足3个基本要求,我们用n表示DAG中的一个node,S(n)表示节点n被调度到的时钟周期,delay(n)表示完成执行节点n所需要的时钟周期数:
1.调度的执行需要在真正执行的开始以后,即S(n) > 0;
2.如果两个节点(n1,n2)间有一条edge相连接,那么需要满足S(n1) + delay(n1) ≤ S(n2),即如果n2依赖n1的执行结果,那么n2不能早于S(n1) + delay(n1)前被调度。
3.指令调度器不能调度超过实际处理器单个时钟周期内能处理的操作类型数。
 
list-scheduling algorithm通过启发式的方法完成指令的调度。
首先根据需要被调度指令间的依赖关系构建DAG,然后将没有依赖项的指令加入到active列表中,active列表中的项即为准备好的,可选的被调度指令。
然后,依据一定的等级规则(比如指令的delay周期)从active列表中选择指令做调度。当一条指令被调度后,需要更新active列表,将不再有依赖项的指令从DAG中加入active列表中。
持续上述过程,直到指令被调度完成。
如果在调度中出现了active列表为空的情况,可能需要插入nop操作。
 
为便于描述,假设以下描述的目标处理器,一次只执行一条三元操作指令。
 
算法步骤:
  1. 1.构建DAG
  2. 2.计算 Latency
  3. 3.指令调度
 
1、构造DAG
DAG构造需要分析数据流依赖关系。
三种依赖关系:
  1. flow dependence or true dependence
  2. antidependence
  3. output dependence
 
DAG中的edge表示数据的流向,DAG中的node代表一种operation。
每个node都有两个属性,分别是操作类型和执行该操作所需要的指令周期。
如果两个nodes,n1和n2间通过一条edge相连接,说明在n2中会用到n1的执行结果。
 
我们通过如下3个结构体来表示dag,dag_node,dag_edge:
 
struct dag_edge {
struct dag_node *child;
/* User-defined data associated with the edge. */
void *data;
}; struct dag_node {
/* Position in the DAG heads list (or a self-link) */
struct list_head link;
/* Array struct edge to the children. */
struct util_dynarray edges;
uint32_t parent_count;
}; struct dag {
struct list_head heads;
};
 
首先我们需要初始一个dag,通过dag_create()来完成,struct dag中是一个链表头,通过这个双向链表,将所有待调度的指令连接起来。
 
struct dag *
dag_create(void *mem_ctx)
{
struct dag *dag = rzalloc(mem_ctx, struct dag);
list_inithead(&dag->heads);
return dag;
}
根据当前需要调度的指令数来,初始化node,如前文所述,单条指令即为一个node。
使用函数dag_init_node()完成对一个node的初始化,并将该node节点加入到dag链表中。
struct dag_node中的成员struct util_dynarray edges表述该node到其他node的edges集合,其是通过动态数组实现的。
 
/**
* Initializes DAG node (probably embedded in some other datastructure in the
* user).
*/
void
dag_init_node(struct dag *dag, struct dag_node *node)
{
util_dynarray_init(&node->edges, dag);
list_addtail(&node->link, &dag->heads);
}
假设我们有N调指令需要调度,在调用执行 dag_create()、dag_init_node()会形成如下所示的结构,即所有node加入到dag的链表中。
 
EXAMPLE:
struct dag_node node[N];
struct dag *dag = dag_create(NULL); for ( int i = 0; i < N; i++) {
dag_init_node(dag, &node[i]);
} Map:
dag->heads <--> node[0] <--> node[1] <--> node[3] <--> ... <--> node[N]
完成了dag和node的初始化后,接下来需要依据nodes间的依赖关系构建edge,最终形成指令间的DAG。
函数dag_add_edge()实现了从parent向child添加一条edge的操作。
edge通过sruct  dag_edge表示,其用于指向child这个node和特定data。
 
函数dag_add_edge()两个参数parent和child代表两个node,完成添加后会有一条边由parent流向child(parent --> child)。
首先,函数函数dag_add_edge()中判断,节点parent已有edge指向child,有则退出函数。否则先将该node从dag header中移除,再将child加入到parent的edges所在的动态数组中,并对child->parent_count++,即依赖计数增加1。
 
根据需要调度指令间的依赖关系,依次调用函数dag_add_edge()后,依然留在dag header中的指令,即为没有依赖项的,准备好了的可调度指令。
 
/**
* Adds a directed edge from the parent node to the child.
*
* Both nodes should have been initialized with dag_init_node(). The edge
* list may contain multiple edges to the same child with different data.
*/
void
dag_add_edge(struct dag_node *parent, struct dag_node *child, void *data)
{
util_dynarray_foreach(&parent->edges, struct dag_edge, edge) {
if (edge->child == child && edge->data == data)
return;
}
/* Remove the child as a DAG head. */
list_delinit(&child->link); struct dag_edge edge = {
.child = child,
.data = data,
}; util_dynarray_append(&parent->edges, struct dag_edge, edge);
child->parent_count++;
}
依据依赖关系构建完DAG后,依旧留在dag->heads中的node就构成了active list。
在开始指令调度前,需要计算出DAG中node的Length of the longest (latency) chain,以下简称latency。
latency的计算是自底向上的遍历各个node,具体计算单个node的delay是通过函数dag_traverse_bottom_up()的回调函数接口实现的,这个需要根据实际的指令集来计算。
 
struct dag_traverse_bottom_up_state {
struct set *seen;
void *data;
}; static void
dag_traverse_bottom_up_node(struct dag_node *node,
void (*cb)(struct dag_node *node,
void *data),
struct dag_traverse_bottom_up_state *state)
{
if (_mesa_set_search(state->seen, node))
return; util_dynarray_foreach(&node->edges, struct dag_edge, edge) {
dag_traverse_bottom_up_node(edge->child, cb, state);
} cb(node, state->data);
_mesa_set_add(state->seen, node);
} /**
* Walks the DAG from leaves to the root, ensuring that each node is only seen
* once its children have been, and each node is only traversed once.
*/
void
dag_traverse_bottom_up(struct dag *dag, void (*cb)(struct dag_node *node,
void *data), void *data)
{
struct dag_traverse_bottom_up_state state = {
.seen = _mesa_pointer_set_create(NULL),
.data = data,
}; list_for_each_entry(struct dag_node, node, &dag->heads, link) {
dag_traverse_bottom_up_node(node, cb, &state);
} ralloc_free(state.seen);
}
完成latency的计算,就要进行指令的调度了。
当前可选的指令处在dag->heads的列表中,即为active list。
从active list中挑出一条指令的规则,一般会通过一个clock来计数当前的执行周期,并根据实际的目标处理器的特点来构建。
当我们从active list中挑出一条具体的指令后,需要将该指令从active list中移除,并且更新active list。因为当一条指令被调度后,对其依赖的指令,如果没有其他依赖的指令没有被调度,那么该依赖的指令需要加入到active list中,该操作由函数dag_prune_head()完成。
 
函数dag_prune_head()首先将被调度的node从dag->heads中移除;然后依次遍历该node的每条edge所指向的node,遍历到这些nodes后,将他们的依赖计数进行更新(减一),如果更新后node的依赖计数等于零,说明该node的依赖项均已被调度,该node会被加入到active list(dag->heads)中。
 
/* Removes a single edge from the graph, promoting the child to a DAG head.
*
* Note that calling this other than through dag_prune_head() means that you
* need to be careful when iterating the edges of remaining nodes for NULL
* children.
*/
void
dag_remove_edge(struct dag *dag, struct dag_edge *edge)
{
if (!edge->child)
return; struct dag_node *child = edge->child;
child->parent_count--;
if (child->parent_count == 0)
list_addtail(&child->link, &dag->heads); edge->child = NULL;
edge->data = NULL;
} /**
* Removes a DAG head from the graph, and moves any new dag heads into the
* heads list.
*/
void
dag_prune_head(struct dag *dag, struct dag_node *node)
{
assert(!node->parent_count);
list_delinit(&node->link); util_dynarray_foreach(&node->edges, struct dag_edge, edge) {
dag_remove_edge(dag, edge);
}
}
 
本文主要是通过对《Engineering a compiler》翻译整理而来。
本文中参考的代码若无特别指出均是来源于mesa中的源文件 src\util\dag.c 和 src\util\dag.c。
 
参考资料:
Advanced compiler design and implementation / Steve Muchnick.

list scheduling algorithm 指令调度 —— 笔记的更多相关文章

  1. Thread Pool Engine, and Work-Stealing scheduling algorithm

    http://pages.videotron.com/aminer/threadpool.htm http://pages.videotron.com/aminer/zip/threadpool.zi ...

  2. SystemVerilog Event Scheduling Algorithm

    While simulating System Verilog design and its test-bench including assertions, events has to be dyn ...

  3. Rate Monotonic Scheduling algorithm

    这篇文章写得不错 http://barrgroup.com/embedded-systems/How-To/RMA-Rate-Monotonic-Algorithm 另外rtems的官方文档也有类似说 ...

  4. 《Algorithm算法》笔记:元素排序(2)——希尔排序

    <Algorithm算法>笔记:元素排序(2)——希尔排序 Algorithm算法笔记元素排序2希尔排序 希尔排序思想 为什么是插入排序 h的确定方法 希尔排序的特点 代码 有关排序的介绍 ...

  5. 操作系统学习笔记(五)--CPU调度

    由于第四章线程的介绍没有上传视频,故之后看书来补. 最近开始学习操作系统原理这门课程,特将学习笔记整理成技术博客的形式发表,希望能给大家的操作系统学习带来帮助.同时盼望大家能对文章评论,大家一起多多交 ...

  6. 操作系统概念学习笔记 10 CPU调度

    操作系统概念学习笔记 10 CPU调度 多道程序操作系统的基础.通过在进程之间切换CPU.操作系统能够提高计算机的吞吐率. 对于单处理器系统.每次仅仅同意一个进程执行:不论什么其它进程必须等待,直到C ...

  7. 【GCC编译器】Swing Modulo Scheduling

    1. SMS 在 GCC 中的实现 1.1. 一些基本概念 (1)软流水(Software pipelining )是一种通过重叠不同迭代的指令,使其并行执行,从而改进循环中指令调度的技术.关键思想是 ...

  8. Least slack time scheduling

    This algorithm is also known as least laxity first. 词语解释:Laxity 松懈的:马虎的:不严格的,Least-Laxity-First 松弛程度 ...

  9. 【传智播客】Libevent学习笔记(四):事件event

    目录 00. 目录 01. 事件概述 02. 创建事件 03. 事件的标志 04. 事件持久性 05. 超时事件 06. 信号事件 07. 设置不使用堆分配的事件 08. 事件的未决和非未决 09. ...

随机推荐

  1. Flink on Yarn三部曲之三:提交Flink任务

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. java 保留小数点后指定位数四种方法

    1 package com.itheima_01; 2 3 import java.math.BigDecimal; 4 import java.text.DecimalFormat; 5 impor ...

  3. 面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  4. django—路由相关

    django不同版本的路由配置 django 2之前,配置urlpatterns使用的是url方法 django 2之后,配置urlpatterns使用的是path方法 path与url的区别: ur ...

  5. redis过期事件,swoole监听

    $serv = new Swoole\Server('0.0.0.0', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set(array( 'work ...

  6. window.open浏览器弹出新窗口被拦截—原因分析和解决方案

    最近在做项目的时候碰到了使用window.open被浏览器拦截的情况,在本机实验没问题,到了服务器就被拦截了,火狐有拦截提示,360浏览器拦截提示都没有,虽然在自己的环境可以对页面进行放行,但是对用户 ...

  7. C. Bank Hacking 解析(思維)

    Codeforce 796 C. Bank Hacking 解析(思維) 今天我們來看看CF796C 題目連結 題目 略,請直接看原題. 前言 @copyright petjelinux 版權所有 觀 ...

  8. Kubernetes 搭建 ES 集群(存储使用 local pv)

    一.集群规划 由于当前环境中没有分布式存储,所以只能使用本地 PV 的方式来实现数据持久化. ES 集群的 master 节点至少需要三个,防止脑裂. 由于 master 在配置过程中需要保证主机名固 ...

  9. active cab inf文件编写

    最近做了一个网页下载控件.主要就是实现ActiveX控件功能. 由于自己是第一次做,不熟悉其过程.中间走了很多弯路.现在把走过得路程记录部分,希望对其他人可以有点用. 首先制作一个你自己的DLL文件. ...

  10. python数据分析03Python的数据结构、函数和文件

    我们会从Python最基础的数据结构开始:元组.列表.字典和集合.然后会讨论创建你自己的.可重复使用的Python函数.最后,会学习Python的文件对象,以及如何与本地硬盘交互. 3.1 数据结构和 ...