实时程序WCET分析的框架
根据当前计算机体系结构和程序设计语言的特性,要达到静态WCET分析,预先获取安全、紧凑的程序最坏情况执行时间的目的,任何WCET分析框架都必须解决由于计算机各种硬件加速特性给程序运行时间带来的不确定性问题和程序通常会根据输入参数的不同执行不同路径,从而使得执行时间不同的问题。
实时系统的时间分析
时间分析始终贯穿着嵌入式实时系统开发的全过程。理想的WCET分析工具,应该作为嵌入式系统集成开发环境的一个组成部分,与编译器、调试器、模拟器和统计分析工具一样,作为实时系统开发程序员手中一个方便的工具。
嵌入式实时软件的时间分析流程及各阶段的目标。
- 在系统需求与设计阶段,一旦确定了嵌入式实时系统的功能需求,就需要对嵌入式处理器型号、主频进行选择。这就需要对系统软件运行的时间需求来进行初步的评估。估计过低,选择的处理能力最终可能无法满足系统运算量的需求;估计过高,会造成处理器资源的浪费,增加硬件成本。最终处理器的结果,还要受到后续时间分析结果的影响。
- 在系统编码阶段,理想的时间分析应该编码和单元测试同步。对程序子模块或单元进行时间分析,既是后续集成时间分析的基础,也是发现系统性能瓶颈的机会。对嵌入式系统来说,单元测试的对象一般为函数或过程。理想的单元时间分析工具应该与单元测试工具无缝集成。
- 在系统集成与测试阶段,任务或程序的WCET估计是单元或子模块WCET的综合。这一阶段的WCET估计需要考虑任务的输入参数约束、各子单元之间的相互依赖关系等,这些信息来源于自动分析或借助人工辅助标注获取。在工业界,传统的方式采用的集成模拟器或如Logic analyzer等分析系统。
- 嵌入式实时系统在最终投入运行前必须得到充分的正确性验证。前述的各阶段的时间分析,或通过静态分析进行,或者通过模拟器工具测量,在最后的正确性验证阶段测试必须在目标系统上实际进行。
- 正确性验证包括:任务执行的逻辑正确性、时间正确性,硬件系统的选择是否能够满足系统异步事件或周期性事件处理的需求等。
实时程序的WCET分析框架
程序的WCET是实时软件时间分析的关键指标。
-
静态WCET分析是指通过分析程序源代码或目标代码和对程序执行的目标机器特性的分析建模,来预先获取程序的最大执行时间的方法。
-
静态WCET分析以程序(源代码或目标代码)和目标机器特性为输入,输出为程序的WCET估计值。估计值的质量高低(主要是指安全性、紧凑性)取决于对程序可能执行路径分析的完备性、准确性以及对目标执行环境特性考虑的充分程度。
-
静态WCET分析主要分为三个阶段:控制流分析、底层分析和WCET计算阶段。
-
控制流分析阶段:通过对程序代码的分析,提取出程序所包含的基本块以及基本块之间的控制流关系。对包含循环的程序还要进一步确定循环的上界,排除多个分支之间可能存在的不可行路径等。控制流分析的目的是找出程序的所有可能控制流路径。
-
底层分析阶段:针对具体的目标执行环境,确定控制流分析所解构出来的基本块的语句或指令序列的执行时间。底层分析必须基于特定的目标硬件体系结构,以目标硬件指令执行时间模型为依据,以基本块语句或指令序列为输入,计算或估计基本块的执行时间。底层分析的主要考虑内容是:缓存、流水线、分支预测。
-
WCET计算阶段:根据前两个阶段所产生的分析结果,运用具体的WCET计算算法,对程序的WCET进行计算。
-
WCET分析的各阶段是环环相扣的。控制流分析必须先于底层分析进行,因为底层分析的分析对象——基本块指令流,由控制流分析产生。最终的WCET计算,是基于程序的控制流路径及基本块的执行时间计算得出。
-
该框架将WCET分析分为:流信息提取、流信息转换、人工干预、执行时间建模和计算五个组成部分。
-
流信息提取、流信息转换和人工干预三个模块都属于控制流分析。因此要达到静态WCET分析的目标,结合当前计算机体系结构、程序设计语言的特征,所有静态WCET分析框架本质上仍然分为:控制流分析、底层分析和计算三个组成部分。
-
当前大多数嵌入式实时系统以C语言为主要开发语言,以开源编译器GCC为主要编译器,以虚拟机和模拟器为主要调试环境的特点。本文给出了一个WCET分析框架,并重点讨论程序控制流分析方法和WCET计算的算法。
-
框架主体上仍然由控制流分析、底层分析和WCET计算三部分组成,框架的输入为源程序、输入参数约束、人工标注、目标机器描述,输出为WCET估计值。整个框架控制流分析、底层分析和计算相互分离,WCET值的计算采用对路径各基本块执行时间求和的方法。
-
WCET分析一般以程序源代码作为分析的输入来提取程序的控制流信息。由程序员书写的高级语言源程序,是对问题解决算法的编程语言描述,更加接近人类描述问题的方式。因此,分析高级语言源程序,更容易提取出程序的控制流信息。
-
WCET分析也可以直接以程序目标代码为分析对象。一些商业工具,如aiT和BoundT,为了使获取的估计更加精确都直接以目标代码为分析对象。目标代码是机器的最终执行的指令,基本块执行时间的估计在目标代码进行,更能获得比较准确的时间估计。而在目标代码上进行控制流分析则比较困难,因为目标机器指令由源程序经过编译、优化后产生,其语义更远离人类描述问题的方式。
-
从源代码入手,分析程序控制流可以借助编译器分析语法、语义的强大功能实现。在编译器上实现控制流分析还可以不用做流信息转换,转换由编译器自动维护;编译器通常都具备有代码优化功能,可以帮助排除一些不可行路径和无用代码等。在编译器上实现控制流分析的缺点是:与编译器紧密绑定,通用性较以目标代码为分析对象的方法差。
-
本方案针对当前是实验室嵌入式系统开发多采用GCC作为编译器的特点,在GCC源代码的基础上作进一步修改来达到控制流分析的目的。
-
GCC是开源编译器,主要针对UNIX、LINUX平台,支持众多编程预压,GCC-Core源代码包是GCC的核心包,是GCC编译器集合中的C语言编译器模块。
-
只针对C语言,源代码量相对较小。内嵌对基本块、控制流图概念的支持。可以将控制流图导出文件,便于下一步分析。对X86、ARM、Motorola。
-
GCC在源程序编译过程中对代码做了基本块划分,生成并维护控制流图。GCC维护这两种结构是为了代码优化和代码生成。在GCC中,基本块和控制流图的概念和结构与WCET分析中概念和结构相似。为了做控制流分析和WCET计算,方案对基本块数据结构做了修改。
-
对基本块做了进一步的性质区分,如:入口。出口、循环条件。
-
增加了基本块时间描述属性,用于记录基本块执行的指令周期数。
-
增加了基本块之间的冲突标志,用于表达基本块之间的互斥关系。
-
对控制流图的边数据结构做了如下修改:对边赋予不同类型,分别记为:普通、分支、循环回边、循环出边。
-
对分支和循环边,记录了分支条件和循环条件以及最大循环次数描述。上述修改是为了做下一步控制流分析所用。
-
流分析对GCC生成的控制流图做进一步的分析,主要包括:循环上界、不可行路径等,以自动分析和人工交互标注相结合。
-
通过对程序目标机器指令的指令周期简单相加可以得到程序执行时间的一个估计。在具有多级缓存、流水线、分支预测等硬件特性的目标处理器上,这种简单指令周期相加的计算方法对于嵌入式实时系统WCET估计可能是想当不准确的。因此,结合目标机器各种加速特性对目标代码进行底层分析,对于获取更为精确的WCET估计是必须的。
缓存
- 尽管对特定的机器体系结果,每条指令的执行时钟周期都是确定的,但指令执行所需的数据存取时间由于缓存等因素的存在,存在较大差距。
- 例如LD R1,X。将变量X的值装入寄存器R1。假设执行该指令的机器为三级存储器体系结构,即缓存,主存,外存,在执行该指令时会存在三种情况。
- 1)X在缓存中,即缓存命中,指令执行速度很快。
- 2)X不在缓存中,但在主存中,指令执行速度稍慢。
- 3)X不在缓存中,且X所在页面失效,即已被交换出主存,指令执行速度很慢(产生缺页异常,指令被中断执行,等待X重新调入内存)
- 一般的嵌入式实时系统为了保证任务运行的时间效率,情况(3)不允许存在,因此研究缓存对执行时间的影响,仅分为缓存命中和不命中两种情况。对给定的基本块指令序列,其执行时间总是介于完全命中和完全不命中之间。对于给定指令,假设存在缓存命中和不命中两种情况下的执行时间是可分别确定的,那么对缓存的研究就集中在给定指令序列缓存命中不命中数量的判断上。
流水线(pipelines)
- 现代处理器为了提高执行效率,指令的执行通常被分为:取指、译码、执行、回写等多个阶段。
- 当第一条指令经过了取指,进入译码阶段时,紧接着第二条指令开始取指。当第一条指令经过译码,进入执行阶段时,第二条指令开始译码,第三条指令可开始取指。处理器的这种特性被称为流水线技术,该技术使得指令在一定程度上可以并行执行,大大提高了处理器的执行效率。
时钟周期/指令流 | 指令1 | 指令2 | 指令3 | 指令4 |
---|---|---|---|---|
1 | 取指 | |||
2 | 译码 | 取指 | ||
3 | 执行 | 译码 | 取指 | |
4 | 回写 | 执行 | 译码 | 取指 |
5 | 回写 | 执行 | 译码 | |
6 | 回写 | 执行 | ||
7 | 回写 |
- 此表展示了4级流水线技术指令执行的情况,假设每阶段消耗的时间相同,并且执行足够多的指令,理想情况下4级流水技术对指令的执行效率将提高4倍。理想的情况是指:指令的执行相互独立,之间不存在依赖关系。
- 当后一条指令的操作数是前一条指令的操作结果,或者后续执行哪一条指令取决于前一条指令执行的结果时,后一条指令必须等待前一条指令执行完毕才能开始执行。流水线的并行执行模式被打乱,进而影响指令总的执行时间,为获取精确的程序执行时间,底层分析必须考虑流水线技术对程序执行时间的影响。
分支预测
- 在指令执行时,条件跳转指令往往依赖于前一条指令执行的结果,决定是否跳转。在具有多级流水线技术的处理器上,为了充分利用这种提高指令执行效率的技术,处理器会对结果提前进行预测,选择一条分支来执行。当预测成功时,指令继续执行,并行执行有效;当预测失效时,指令停止执行,需要重新获取指令执行。
- 两种预测结果的指令执行总时间是不同的,为获取精确的程序执行时间,底层分析同样必须考虑分支预测技术对程序执行时间的影响。
- 理想的底层分析工具应该适用于各类目标平台,以目标机器特性描述和需要分析的目标代码作为输入参数,经过底层分析器得出目标代码执行时间。
WCET计算 - WCET计算阶段通过结合控制流分析阶段和底层分析阶段的结果来计算程序的WCET值,主要有三种计算方法。
- 基于隐藏路径枚举的计算方法。该方法将程序路径和各基本模块的执行时间用代数和逻辑的限制条件表示。
- 最坏情况执行时间在满足这些限制条件的前提下通过最大化目标函数来获得。
- 计算时,采用了时间图模型,每个结点定义了两个变量,分别表示该结点的执行次数和执行时间。通过使用结构性限制条件给出程序结构中可能的路径。还有一些限制条件是通过关于可行与非可行路径信息给出的。其中一些限制条件是强制的,比如循环上界,而其他限制条件仅仅为了使得最终计算结果更加精确,比如程序中不可能执行路径的信息。
- 基于隐藏路径枚举的计算方法的主要优点在于分析中不需要显示枚举程序路径。IPET方法可以处理任何程序,意味着不需要严格限制程序结构。缺点是不能指明哪一条执行路径具有最大执行时间。很难包含进底层分析,如缓存和分支预测,影响了WCET估计精度,最大化求解主要的方法是整数线性规划LIP。
- 基于树的计算方法,将程序结构用时间树或语法树模型表达,并通过自底向上地对代表不同控制流结构的子树进行计算来得到程序的WCET。这种方法的优点是概念简单,计算代价低。但是时间树模型要求程序必须遵循良好的结构,对存在像goto这样的可任意跳转的程序结构用时间树表达并不奏效。
- 基于路径的方法。通过计算程序中的不同路径的执行时间,找到具有最长执行时间的路径来计算最终的WCET。
- 通过对控制流图的各条边赋予代表基本块执行时间的加权值,然后利用图的路径查找算法,遍历控制流图从起点到终点的所有可能路径,每条路径所经过的边的加权和代表了程序该路径的执行时间,通过比较各路径执行时间,即能估计出程序的WCET。当程序包含较多地分支、循环结构时,基于路径的计算算法的时间复杂度,将随分支和循环次数呈指数增长。
程序控制流分析
- 程序控制流分析是WCET分析的基础,程序控制流分析的目的是获取程序的可能执行路径。找到与程序实际WCET对应的执行路径,才能对WCET进行估计。
- 对程序控制流分析,首先要对程序结构进行逻辑划分。基本块、控制流图、时间树是程序逻辑结构非常有效的表示模型。循环上界确定与估计、不可行路径分析是获取精确WCET的关键。
基本块
- 基本块是程序语句或指令的一个划分,在基本块内的语句或指令组成了直线系列,执行时仅有一个路口和一个出口,语句或指令在基本快内的出现顺序就是执行时的顺序。
L1:x = x + 1;
L2:y = x + 2;
L3:z = x + y; - 这三条语句组成了一组直线代码序列,一旦处理器控制权转移到了语句L1处,处理器就会依次执行后续的L2、L3语句。基本块就是由这样的语句序列组成,一旦控制权转移到基本块入口处,那么处理器将在基本块内按顺序逐一执行指令,直至运行到基本块的最后一块指令。因此,基本块内除最后一条指令外,不会有跳转(jump)、条件转移等指令的存在。
- 对给定的程序,按一定的划分算法,可以将程序划分为由多个基本块的构成。因为基本块是直线代码序列,在分析程序控制流时,可以作为一个整体看待,而不用考虑其内部控制流。这样对给定程序的控制流的分析,可以进一步简化为对该程序基本块之间的控制流分析。
- 一个求两个整数差的绝对值的简单程序可以被分为三个基本块。
- 一段程序或一个函数通常会被划分为多个基本块。多个基本块的存储可以选择数组或者双向链表等数据结构。基本块用结构体来描述,基本块除了要记录所包含的语句和指令外,还需要记录其相连的边、基本块类型、执行时间等信息。
- 本文采用基于双向链表的基本块数据存储结构,给出了求两个整数差的绝对值函数f的基本块链表。
- 程序中的每一个函数都对应一个基本块链表。
基本块结构体C语言定义
struct basic_block{
stmt stmt_list; //基本块包含的语句序列
struct cfg_edge *preds; //入边指针
struct cfg_edge *succs;//出边指针
enum bb_type{ //基本块类型标志
normal = 0; //普通
start = 1; //入口
exit = 2; //出口
loopcond = 3; //循环条件
loopbody = 4; //循环体
}type;
int conflict_flag; //用于分支依赖相关的标记
int exec_time; //基本块执行时间
struct basic_bock *prev_bb; //双向链表前驱指针
struct basic_block *next_bb; //双向链表后驱指针
}
基本块划分
- 输入:一个简单语句序列。
- 输出:一个基本块列表。
- 首先确定入口语句(即基本块的第一条语句)的集合:(1)第一条语句是入口语句。(2)任何能由条件转移语句或无条件转移语句转移到的语句都是入口语句。(3)紧跟在转移语句或条件转移语句后的语句都是入口语句。
- 对于每条入口语句,其基本块由它和直到下一条入口语句之间的语句或直到程序结束语句之间的所有语句组成。
- 基本块划分可以在代码的源代码表示层进行,也可以在目标代码层进行。在源代码层进行,对于提取影响控制流的约束信息更加方便,但编译器会在代码生成进行各种优化,这些优化包括对基本块的变换,对分支、循环的优化等,优化的结构会改变程序的结构。
- 因此,如果基本块是在源代码划分的,还必须随着编译的进行,对基本块进行变换。
- 在目标代码上划分基本块,尽管不需要再做变换,但其在目标代码上获取流信息更加困难。
- 依据上述基本块划分算法,对求两个整数差的绝对值的简单程序进行划分。
int f(int x,int y)
{
int i,j,s;
s = 0;
i = x;
j = y;
if( i > j)
{
s = i - j;
return s;
}
else
{
s = j - i;
return s;
}
}
- 首先确定入口语句。s = 0;为函数的第一条语句,所以它是入口语句。s = i - j;为紧跟在分支语句if(i > j)后的第一条语句,所以它是入口语句。s = j - i;为分支语句if(i>j)可以直接跳转的语句,所以它是入口语句。
- 然后对每条入口语句,寻找其它同它一起构成基本块的语句。
第一个基本块 | 第二个基本块 | 第三个基本块 |
---|---|---|
s = 0; i = x; j =y; if (i >j) | s = i -j; return s; | s = j - i; return s; |
基本块时间估计
假设基本块由i条指令组成,对该基本块的执行时间估计可以由公式:∑Texec(li)给出
- Texec(li)表示第i条指令li的执行时间。基本块的执行时间为块内各条指令执行时间的和。基本块执行时间估计公式从表面上看既容易理解,又便于计算,但简单运用该公式并不能获得比较理想的基本块执行时间估计值,甚至与实际的执行时间相去甚远。产生偏差的原因来自于缓存、流水线、分支预测等硬件加速特性的影响。上述计算公式是不够的,必须依赖于考虑了缓存、流水线等因素的目标执行环境的执行时间模型。
控制流图
控制流图是一种用于表示程序逻辑结构的有向图。控制流图表示为CFG=(V,E),V表示图的结点集,每一个结点代表程序的一个基本块;E表示图的有向边集合,每一条边由序偶对<x,y>表示,x表示边的源结点,y表示边的目的结点,边表示了其所连接的基本块之间存在的控制流关系。
- 控制流图的结点代表了程序的基本块,因此根据基本块的类型划分,控制流图的结点也被分为了对应的类型。
- 控制流路径:对控制流图CFG,其控制流路径表示为CFP。控制流路径是指从控制流图的入口结点Vstart出发,沿着有向边经过一系列结点Vp1,Vp2,Vp3,…Vpm,到达某一出口结点Vc所经过的结点序列(Vstart,Vp1,Vp2,Vp3,…,Vpm,Ve)。其中代表入口基本块的Vstart对特定的控制流图仅有一个,但代表出口块的结点Ve可能存在多个,且对任意出口结点Ve,从Vstart到Ve都可能存在多条路径。因此控制流图CFG的控制流路径CFP是一个路径的集合。
- 求1~100之间能被3整除的所有整数
int f()
{
int i,s;
i = 1;
s = 0;
while(i <= 100)
{
if(i%3==0)
{
s = s + i;
}
i++;
}
return s;
}
该程序控制流程图的形式化表示CFG = (V,E),其中基本块集合V = {B1,…,B6};
有向边={<B1,B2>,<B2,B3>,<B3,B4>,<B3,B5>,<B2,B6>}
该控制图的图形化表示:
控制流图以基本块和基本块之间的关系有效地表达了程序的逻辑结构,对于进一步的控制流分析具有重要意义。
- 上述程序可能的控制流路径有:B1-B2-B6,B1-B2-B3-B5-B2-B6,B1-B2-B3-B4-B5-B2-B6。
- 程序的WCET就是从所有路径的执行时间中估计得来的。但在程序的控制流图中,由于环的存在,使得从入口到出口的路径长度是变长的,甚至是无限长的。结点B2,B3,B5构成环;结点B2,B3,B4,B5构成环。
- 控制流图中存在环,说明在程序中存在循环结构,如while,for,do…while等,或者程序由于goto语句,构成了隐式循环。
- 控制流分析必须仔细地对循环的最大次数进行估计,无法估计循环最大次数也就无法得出最坏情况执行时间。
控制流图边cfg_edge结构体定义:
struct cfg_edge
{
struct basic_block *src; //边的源基本块
struct basic_block *dest; //边指向基本块
edge__type{ //边的类型标志
straight = 1; //直线边
loop = 2 //循环边
loopExit = 3; //循环结束边
branch = 4; //分支边
call = 5; //函数调用
}type;
int exec_count; // 边的执行次数,普通边为1
} // 循环边为循环最大次数
控制流图control_flow_graph结构定义:
struct control_flow_graph
{
struct basic_block * entry_block_ptr; // 入口基本块指针
struct basic_block * exit_block_ptr;// 出口基本块指针
int n_basic_blocks; // 基本块的数量
int n_edges; // 边的数量
struct basic_block * last_basic_block; //空闲基本块指针
enum profile_status {
PROFILE_ABSENT;
PROFILE_GUESSED;
PROFILE_READ
}x_profile_status;
};
控制流图生成
- 控制流图常用来描述一个函数的逻辑结构,即每一函数都对应着单独的控制流图。控制流图与源程序的语法结构密切相关,因此控制流图的生成可以在编译器对源程序的语法结构分析过程中完成。
- GCC编译器在语法分析技术上采用了递归下降的语法分析方法。
- 递归下降分析方法是一种自顶向下的语法分析方法,通过当前读到语法关键词来判定后续源代码的语法结构,进而调用相应的分析函数来分析语法。
- 当语法分析器遇到if或while关键词时,它判断接下来是if分支结构或while循环结构,进而调用相应的分析函数进行分析。
- 递归下降分析法对if、for、while和switch等程序的控制结构非常有效,因为这些结构具有明显的词法特征。
- 下图描述了编译器在语法分析过程中如何生成和维护控制流图的过程。
- 控制流图表达的就是程序的控制结构,因此在编译器语法分析同时生成控制流图。
时间树
- 时间树(Timing tree)是一种树数据结构,树的每片叶子结点代表程序的一个基本块,并保存了基本块的执行时间。内部结点保存了程序的控制流结构信息,如顺序、循环、分支、返回、调用等信息。
- 时间树是程序逻辑结构除控制流图外另一种有效的描述方式。树是一种基本的数据结构,由树结点和边构成,程序的时间树与编译器生成的语法树相似。
- 时间树中矩形结点表示叶子结点,T1-T5与基本块B1-B5对应,T1表示基本块B1的执行时间。圆圈表示内部结点。内部标识SE、BR、LP代表结点与各子树之间的关系。
- SE:表示该内部结点的子树之间是一种从左至右的顺序关系。
- BR:表示该内部结点的子树之间是一种条件选择关系,即子树中仅有一个会满足条件。
- LP:表示其子树是一个循环体。
时间树内部结点除了记录子树之间的关系外,还详细记录了关系的具体约束条件。
时间树timing_tree结构体C语言定义
struct timing_tree
{
struct tt_node root; //时间树根节点
int n_basic_blocks; //基本块的数量
int WCET; //WCET估计值
}
struct tt_node
{
tt_node_type node_type; //结点类型
int exec_count; //LP型的内部结点,代表循环的最大次数
int exec_time; //基本块执行时间
struct tt_edge *child; //指向子树的边链表头指针
}
struct tt_edge
{
struct timing_reee *childNode; //边指向的时间子树
struct tt_edge *next; //构造边双向链表所需指针
struct tt_edge *pre;
}
enum tt_node_tyoe
{
SE = 1; //顺序关系的内部结点
BR = 2; //分支关系的内部结点
LP = 3; //循环体的内部节点
BB = 4; //基本块的叶节点
}
- 时间树体现了“自顶向下”的程序结构分析思想。首先将要分析的程序做粗略的划分,划分出的各部分之间仅有一种关系:顺序、分支或循环。再对划分出的各部分利用相同的方法做进一步的划分,直到划分的各子树都为不可再分的基本块。
- 只有结构良好的程序,才能创造时间树。因为时间树的任意结点最多仅有一条入边,为了使基于时间树的WCET计算更加简单。准确,本文对使用时间树表示的程序结构做如下规定:
- 1.程序中不允许存在像goto这样的可任意跳转语句。
- 2.程序仅有一个出口,仅程序仅在最后一条语句返回。
- goto语句在结构化程序设计中是明确限制的。对于程序仅有一个出口的限制,可以通过仔细调整程序结构做到。
- 利用程序时间树计算WCET,体现“自底向上”的问题求解思想。首先计算各子结点的WCET,再逐步向上计算父节点的WCET,直到计算树的根节点,最终结果即为整个程序的WCET。
循环上界估计
- 对于包含循环的程序,循环体的执行次数对程序执行时间的影响显著,因此要获得精确的WCET估计值,必须对循环的上界数进行仔细考察。Ada、Euclid等实时编程语言对循环结构的使用做了明确规定,限制如while条件循环等无法确定循环次数的结构的使用,当循环无法自动判断执行上界数时,必须由人工标注指出循环上界数。
- 对于循环体A,设TA为一次循环的最大执行时间,CA为循环的最大次数,那么A的最大执行时间:WCET(A) = TAxCA。但对包含分支的循环体,该公式的估计结果可能是想当不准确的。
- 求1-100的偶数和
int sum()
{
int i,s;
i = 1;
s = 0;
while(i <= 100)
{
if (i % 2 == 0)
s = s + i;
i++;
}
return s;
}
- 为方便计算,假设基本块B2,B3,B4,B5的执行时间均为1,循环体的最大执行时间路径B2->B3->B4->B5的执行时间TB2 + TB3 + TB4 + TB5 = 4,循环的次数为100,按照上述公式就是:WCET(A) = TAxCA = 4x100 = 400
- 实际上奇数时,执行的路径为B2->B3->B5,两条路径各执行了50次。实际的执行时间:WCET(A) = 3 x 50 + 4 x 50 = 350.
- 产生上述计算结果差异的原因是:循环体包含了分支,且分支条件与循环条件相关。因此,要获得比较精确的WCET必须对循环体做严格限制:1)循环体内部,尽量不要使用分支、循环等结构。2)如果有分支、循环,要尽量避免二者条件相关。
- 通过对上述代码改写,可以避免在循环体内部出现分支结构。
int sum()
{
int i,s;
i = 2;
s = 0;
while(i <= 100)
{
s = s + i;
i = i + 2;
}
return s;
}
- 自动循环上界分析,必须要有严格语法结构的支持。对无法从语法结构上判断的循环,通常通过手工的方法来标注出循环上界。
- 编译器支持的WCET分析工具,可在源代码内部标注。独立于编译器的WCET分析工具,通常以提供单独的标注文件或以交互的方式进行。
不可行路径分析
- 可行路径是程序在运行时所执行过的语句序列,表现在程序控制流程图上就是从入口至出口所穿越过的基本块序列。
- 一段程序从程序的控制结构上存在很多条执行路径,但由于各分支在分支条件上可能存在相互依赖关系,并非所有路径都是可行的。
- 在WCET分析中包含进不可行路径,可能会使最终的估计值过高。
- 基于隐藏路径枚举技术(RPET-based)的WCET分析从程序CFG图中自动计算出程序结构性约束和程序功能性约束(分支约束),然后通过使用整数线性规划(ILP)来解决分支冲突。
- 手工标注是解决分支冲突问题的辅助方法,通过人工判断,将存在冲突的基本块附上相同冲突标志,在基于路径的计算时通过检测冲突标志来排除不可行路径。手工标注的方法可以解决自动分析难以解决的问题,但需要程序员个人的分析判断,工作量大,存在分析判断错误的问题。
- 流信息是影响程序控制流路径的信息,如:分支条件,循环条件,跳转条件等。流信息主要来源于对程序代码的语法和语义分析,或通过人工辅助标记提供的信息。人工辅助标记即可以在源代码内部插入辅助标记,也可以提供单独的标记文件。人工标记也可以以交互的方式进行。
- 在理想情况下,流信息可以从程序源代码语法和语义信息中自动抽取。语义分析可以利用数据和控制依赖、抽象解释等方法。
- 当通过自动分析获得的流信息,不足以为计算精确的WCET提供足够信息时,就要提供附加的手工标记信息。手工标记往往针对难以确定的循环上界,隐含的分支依赖等,这些信息很难通过自动语法和语义分析获得。
WCET计算算法
- 静态WCET分析的目的是对程序的最坏情况执行时间进行估计。在完成控制流分析和底层分析后,就需要根据上述两步分析结果,按照一定的计算算法对程序的WCET进行估计计算。
- 第三章详细分析了用于表示程序控制流结构的两种表示模型:控制流图和时间树,用模型表达程序的最终目的是为了最终的WCET计算。
- 基于路径的算法以控制流图模型为基础,通过计算控制流图中可能存在的路径的执行时间来估计程序的WCET。
- 基于树的算法以时间树模型为基础,通过自底向上的计算时间树各子树所代表的程序结构的执行时间来计算整颗时间树的执行时间。
基于路径的算法
- 基于路径(Path-Based)的WCET计算算法是指:通过遍历程序控制流图上所有可能的执行路径,计算各路径的执行时间,从中找出执行时间最长的路径,该路径的执行时间作为程序的WCET估计值。
- 基于路径的计算算法在计算前必须标识出程序控制流图中各基本块的执行时间(估计值)和确定循环的上界次数,计算过程分以下两步进行:
- 1.消除控制流图中的循环。消除循环,即通过将循环体的所有基本块执行时间乘以循环的最大循环次数,然后消除循环边,化简控制流图。目的是降低路径查找的时间复杂度。
- 2.计算各路径的执行时间,获取WCET值。在消除了循环的控制流图中,利用图的路径查找算法计算WCET。
- 上图a描述了程序的控制流图,图中标识出了各基本块的执行时间和循环的最大次数,图b是对图a简化的结果,基本块B5,B6的执行时间被乘以了循环的最大次数,循环边不再出现在控制流图中。图c灰色的基本块指出了在该控制流图上的最长路径。
- 基于路径的算法时间复杂度会随着分支数目的增长,呈指数增长趋势。当程序包含较多分支时,该算法会具有很高的时间复杂度。
- 算法4.1:移除控制流图循环边
输入:控制流图
输出:无循环边的控制流图
过程描述:对控制流图中的所有循环边,确定每一循环体的所包含的基本块集合,对集合内所有的基本块,将其执行时间乘以可能循环最大次数。移除循环边和循环结束边。
Procedure removeLoopEdges
Beigin
//对控制流图的每一条循环边执行如下操作
foreach edge in LoopEdges:
令strat为该循环体的入口;
令exit为该循环体的出口;
令count为该循环的最大执行次数
// 对该循环的每一个基本块进行如下操作
foreach bb from start to exit
if 基本块为循环条件类型
设置bb.execTime = bb.execTime*(count+1);
else
设置bb.execTime = bb.execTime*count;
endif
endfor
从控制流图中移除该循环边;
从控制流图中移除该循环结束边;
endfor
end
- 基于路径的WCET计算
输入:无循环的控制流图CFG
输出:WCET估计值
过程描述:该算法通过一个递归过程,遍历化简后控制流图可能的执行路径,并在遍历的过程中计算每一条路径的执行时间,记录下已计算的最大的执行时间,当遍历完所有路径后,该时间便是最终的WCET结果。
令WCET为全局变量,初值为0
procedure getWCET(CFG cgf)
begin
设置保存当前WCET计算值的变量curWCET=0;
令start为控制流图的入口基本块;
调用计算过程 CalculationWCET(start,WCET,curWCET);
end
procedure CalculationWCET(BasicBlock bb,WCET,int curWCET)
begin
if 当前基本块与已访问的基本块冲突
return;
else
//将当前基本块冲突标志添加标志集合
AddConflictFlags(bb.flags);
设置curWCET = curWCET + 当前基本块执行时间;
if 当前块无出边
if WCET<curWCET:
WCET = curWCET;
return;
endif
else
//对当前块出边指向的所有基本块继续调用该过程
foreach bbChild in bb.Childrens:
CalculationWCET(bb1,WCET,curWCET)
基于树的算法
- 基于树的计算方法是指自底向上遍历程序时间树的计算方法。通过自底向上不断对时间树的子树进行计算,直至树的根节点,最终的计算结果便是时间树代表的程序的WCET估计值。同样在使用该算法前必须表示出树各叶结点(基本块)的执行时间估计和循环内节点的最大循环次数。
- 图a代表某程序的时间树,图b对分支BR结点类型的子树进行计算,计算公式为各子树时间值的最大值,T6 = max(T2,T3);图c对LP循环类型的子树进行了计算,计算公式为子树的执行时间乘以循环次数,T7 = T4*LP.count;最后图4对顺序SE子树进行了计算,计算公式为各子树时间相加,T8 = T1+T6+T7+T5;即T8为该时间树的计算结果。
- 时间树的内结点代表了程序的逻辑结构关系,顺序、分支、循环是程序的三种基本结构。
结构 | 计算公式 |
---|---|
顺序SE(B1,B2,…,Bn) | T(SE(B1,B2,…,Bn))= T(B1)+ T(B2) + T(Bn) |
分支BR(B1,B2,…,Bn) | T(BR(B1,B2,…Bn)) = MAX(T(B1),T(B2),T(Bn)) |
循环LP(Bcondi,Bbody) | T(LP(Bcondi,Bbody))= (T(Bcondi)*(LP.count+1))+ (T(Bbody) * LP.count) |
- 基于树的WCET计算
输入:时间树tt
输出:WCET估计值
过程描述:算法由递归过程实现,对于顺序结构子树,子树的WCET为根节点的各子树WCET的和;对于分支结构子树,子树的WCET为子树根节点各子树WCET的最大值;对于循环结构子树,子树的WCET值乘以循环最大次数;对于叶结点,WCET为该结点的WCET值。
令WCET为全局变量,初值为0
procedure calculation_WCET_tt(tt_node root)
begin
if roo.node_type == SE
WCET = sum(root.ChildTree.WCET)
return WCET;
endif
if root.node_type == BR
WCET = Max(root.ChildTree.WCET)
return WCET
if root.node_type == LP
WCET = root.ChildTree.WCETxroot.exec_count
return WCET
ENDIF
if root.node_type == BB
WCET = root.exec_time
return WCET;
endif
endif
- 基于树的计算算法的优点是概念简单,容易理解。因为计算的时间复杂度仅与时间树的内部结点个数相关,计算代价相对较低。但该算法在计算过程中各子树独立进行运算,没有路径的概念,因此算法难以处理可能存在的分支依赖关系。最终的计算结果可能包含了不可行路径的结果而过于悲观。另外,该算法受时间树结构的限制,难以处理复杂的程序结构。
算法比较
- 基于路径和基于树的两种计算算法是建立在两种不同模型上的WCET计算算法,对这两种算法进行比较不能脱离其所依赖的模型。
指标 | 基于路径(Path-Based) | 基于树(Tree-Based) |
---|---|---|
基本模型 | 有向图 | 树模型 |
表达能力 | 灵活,表达能力强 | 不能表达任意连接关系 |
程序结构 | 不限制程序结构 | 只能表达良好结构的程序 |
方法 | 计算各路径执行时间 | 对各子树执行时间的综合 |
分支依赖 | 计算过程能考虑分支依赖 | 难以包含分支依赖关系 |
底层分析 | 可在路径上进一步分析各基本块之间的底层相互影响 | 不能给出显示路径,难以进一步做底层分析 |
时间复杂度 | 时间复杂度随循环、分支数量呈指数增长趋势 | 时间复杂度与树内结点呈线性正比关系 |
实验
- 针对当前嵌入式系统开发的特点,本文提出了与编译器紧密结合的WCET分析框架。就该分析框架和基于对WCET分析技术的研究,在GCC开源编译器的基础上进行了静态WCET分析技术的实验。
- 实验平台:实验在基于ARM7TDMI处理器的三星S3C44B0X开发板进行。ARM7TDMI处理器是32位通用ARM处理器,最高主频75MHZ。其特点是