作者: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. javaSE、javaEE、Android知识点总结

    曾今上学时候的一些学习总结,如有错误请大家指出,共同学习. 1. 什么是WebView? WebView是一个使用WebKit引擎的浏览器组件,用来加载网页. 2. WebView中加载网页的两种方式 ...

  2. poj2187 Beauty Contest (凸包 + 旋转卡壳)

    Beauty Contest Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 38349   Accepted: 11851 ...

  3. django—csrf中间件校验流程

    CSRF(跨站请求伪造)是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法. 这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求 ...

  4. MySQL设置慢查询

    MySQL的慢查询日志是用来记录在MySQL中响应时间超过阀值的语句,则会被记录到慢查询日志中(运行时间超过long_query_time值的SQL语句):   慢查询相关参数: slow_query ...

  5. Java 复制到剪贴板

    public void copy(String str) { StringSelection stsel = new StringSelection(str); Toolkit.getDefaultT ...

  6. Reading Face, Read Health论文阅读笔记

    摘要: 随着计算技术觉.人工智能和移动技术的发展,利用计算机读脸技术去识别每个人每天的健康是可行的.怎么去设计一个基于FRT(face reading technologies)的用于得到每天的保健实 ...

  7. Luogu P4105 [HEOI2014]南园满地堆轻絮

    题解 传送门 其实只要找差距最大的逆序对就好了 答案就是此逆序对的差 /2 代码 (代码很短) #include<bits/stdc++.h> using namespace std; # ...

  8. Luogu P4208 [JSOI2008]最小生成树计数

    题意 给定一个 \(n\) 个点 \(m\) 条边的图,求最小生成树的个数. \(\texttt{Data Range:}1\leq n\leq 100,1\leq m\leq 10^4\) 题解 一 ...

  9. Java学习的第四十天

    1.例4.1在其他函数中调用主函数 package bgio; public class cjava { public static void main(String[] args) { prints ...

  10. Linux下开发环境的搭建(For C++ OIer)

    说句实话,对于OIer来说,Linux真的是个很好的开发平台. 这里既没有游戏的喧嚣,也没有广告的打扰,gcc/g++早已预装,一切已为你准备好......(???)即使对于日常使用,也绰绰有余. 如 ...