作者: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. fish_redux使用详解---看完就会用!

    说句心里话,这篇文章,来来回回修改了很多次,如果认真看完这篇文章,还不会写fish_redux,请在评论里喷我. 前言 来学学难搞的fish_redux框架吧,这个框架,官方的文档真是一言难尽,比fl ...

  2. 一篇文章 图解Python 玩转Python

    0 Python 解释器:1.Python数据结构:2.变量与运算符3 Python 流程控制 4 Python 文件处理5 python 输入输出6 Python 异常7 Python 函数和模块8 ...

  3. ng中的ng-content ng-template ng-container

    在angular中,有这样三个自带的标签,但是在angular的文档中没有说明,只有在api中有简单的描述,摸索了半天才搞懂是咋回事. ng-content <div> <ng-co ...

  4. Spring boot ConditionalOnClass原理解析

    Spring boot如何自动加载 对于Springboot的ConditionalOnClass注解一直非常好奇,原因是我们的jar包里面可能没有对应的class,而使用ConditionalOnC ...

  5. java并发编程与多线程基础学习一

    学习url:https://www.cnblogs.com/lixinjie/p/10817860.html https://www.cnblogs.com/JJJ1990/p/10496850.ht ...

  6. [UOJ 275/BZOJ4737] 【清华集训2016】组合数问题 (LUCAS定理的运用+数位DP)

    题面 传送门:UOJ Solution 这题的数位DP好蛋疼啊qwq 好吧,我们说回正题. 首先,我们先回忆一下LUCAS定理: \(C_n^m \equiv C_{n/p}^{m/p} \times ...

  7. 力扣 - 232. 用栈实现队列.md

    目录 题目 思路 代码实现 复杂度分析 题目 请你仅使用两个栈实现先入先出队列.队列应当支持一般队列的支持的所有操作(push.pop.peek.empty): 实现 MyQueue 类: void ...

  8. 数据结构 - 二叉树的遍历(递归VS非递归)

    import java.util.LinkedList; public class BinaryTree { public static void main(String[] args) { int ...

  9. MapStruct 解了对象映射的毒

    前言 MVC模式是目前主流项目的标准开发模式,这种模式下框架的分层结构清晰,主要分为Controller,Service,Dao.分层的结构下,各层之间的数据传输要求就会存在差异,我们不能用一个对象来 ...

  10. expect ':' at 0, actual = (JSON转化异常解决)

    这个报错我的问题主要是前端得到的JSON格式不是标准的JSON串,所以会报这个错, 解决办法 需要使用JSON.toJSONString()转换为标准的字符串