文章目录
程序级性能分析
- 程序性能
≠
\neq
= CPU性能
- CPU时钟频率不是衡量程序性能的可靠度量
- CPU流水线和高速缓存在程序扮演窗口的作用
- 流水线和高速缓存影响执行时间,但是执行时间是程序的
全局特性
- 现实中程序的执行时间很难精确地确定
- 输入数据的变化:数据不同,选择的执行路径不同
- cache的影响:cache的行为依赖与输入的数据
- 指令级的执行时间的不同
- 正常的执行流水可能引入数据依赖性的改变
- 指令的执行依赖于流水中的前后指令
- 衡量程序性能的方法:
- 运用仿真器仿真程序的执行时间
- CPU的状态可见
- 用定时器测量CPU真实的时间
- 需要修改程序控制定时器
- 用逻辑分析仪测量真实的CPU执行
- 在引脚上需要事件可视
- 运用仿真器仿真程序的执行时间
- 程序性能的衡量度量
- 平均执行时间
- 典型数据期望的典型执行时间
- 最差执行时间
- 程序花费在任何输入序列中的最长运行时间
- 对有时限要求的系统是非常重要的
- 最好执行时间
- 程序花费在任何输入序列中的最短运行时间
- 测量多速率实时系统时非常重要
- 平均执行时间
程序性能的要素
- 基本程序执行时间公式:
执 行 时 间 = 程 序 路 径 + 指 令 执 行 时 间 执行时间=程序路径+指令执行时间 执行时间=程序路径+指令执行时间 - 路径:程序执行的指令序列
- 路径考虑了数据的依赖性、流水线行为和cache
- 指令执行时间由程序路径上的指令序列决定
示例:if 语句中的数据依赖路径
考虑如下嵌套 if 语句if (a || b) { /* T1 */ if ( c ) /* T2 */ x = r*s+t; /* A1 */ else y=r+s; /* A2 */ z = r+s+u; /* A3 */ } else { if ( c ) /* T3 */ y = r-t; /* A4 */ }
全部控制变量的结果如下表
此时可发现,只有4种不同情况:无赋值,赋值4,赋值2和3,赋值1和3
- 用指令数目乘以指令的执行时间得到程序的总体执行时间:😅过于简单
- 并非所有的指令都花费相同的时间
- 多周期指令
- 指令的执行时间不是独立的
- 指令的互锁
- cache的影响
- 依赖于操作数的值
- 浮点数计算
- 多周期整数操作
- 并非所有的指令都花费相同的时间
测量驱动的性能分析
- 确定程序执行时间最直接的因素是测量
- 为了使程序在最坏/好执行路径上执行,需提供适当的输入
- 需要访问CPU或者CPU仿真器
- 程序执行的路径记录时程序轨迹(program trace),有时简写为轨迹(trace)。
- 为确定输入值真实有效,可以通过使用基准数据集或者从正在运行的系统中捕获的数据来生成典型输入值。对于简单的程序,可以通过分析算法来确定导致最坏情况执行时间的输入集。
- 方法
- 反馈程序
- 轨迹驱动测量
- 物理测量
- CPU仿真
- 反馈程序
- 输入数据
- 写软件脚手架(software scaffolding),将数据输入程序并得到输出结果
- 软件脚手架(software scaffolding)是将数据传入程序并获取数据输出的辅助代码。
- 它可以在设计大型系统时,很难提取出软件的某一部分,并将其独立于系统的其他部分进行测试。因此可能需要向系统软件中添加新的测试模块,来引入测试数据并观察测试的输出。
- 系统添加新的测试模块,以引入测试值并观察测试输出
- 轨迹驱动的测量
- 将确定程序的执行路径与路径计时融合
- 程序执行,选择一条路径,观测执行时间。
- 轨迹文件较大
- 主要用于对cache的分析
- 物理测量
- 观测程序计时器的值
- 程序开始,启动一个计时器
- 程序结束,停止计时器
- 需要修改程序
- 逻辑分析仪测量引脚的行为
- 通过地址总线查找事件
- 修改代码以使得事件可见
- 由于逻辑分析仪的内存有限,只能对短执行时间的程序有效
- 观测程序计时器的值
- CPU仿真
- 可利用周期精确仿真器
- 可确定执行所需的时钟周期数
- 比实际的CPU慢
- 指令级模拟器
- 只提供功能模拟指令
- 不提供时序信息
- 可利用周期精确仿真器
软件性能优化
- 嵌入式系统需要满足时限要求
- 分析执行时间
- 最坏执行时间
- 提高其执行时间
- 代码级优化
- 指令级优化
循环优化
代码移出(code motion)
- 如果计算结果不依赖于在循环体中执行的操作,可以安全地将它从循环体中移出
- 例如
for (i=0; i<N*M; i++)
z[i] = a[i] + b[i];
👇
x=N*M;
for (i=0; i<x; i++)
z[i] = a[i] + b[i];
归纳变量消除(induction variable elimination)
归纳变量
是从循环迭代变量导出的值(循环的索引)- 考虑以下循环
for (i=0; i<N; i++)
for (j=0; j<M; j++)
z[i,j] = b[i,j];
- 可以用i*M+j表示数组的下标
for (i=0; i<N; i++)
for (j=0; j<M; j++)
{
zbinduct=i*M+j;
*(zptr+zbinduct) = *(bptr+zbinduct);
}
强度消弱
y=x*2;
变为y=x<<1;
cache 优化
- 优化循环嵌套
- 改变数组元素的访问顺序
- 为编译器后一阶段带来新的并行时机
- 改进cache性能
数据重排和数组填充
- 考虑以下代码的cache行为
for(j=0;j<M;j++)
for(i=0;i<N;i++)
a[j][i]=b[j][i]*c;
- 假定:
- 数组a,b大小为M=256,N=4,a=1024,b=4099
- cache是256行,4路组相联,每行4字
- 执行过程中会出现:
- a和b数组起始元素在cache同一行。
- 每四次迭代出现冲突。
- 解决办法;
- 移动一个数组;让b数组的起始地址开始于4100
- 填充数组;把填充字符放在数组行的开始位置,让b数组的起始地址开始于4100
性能优化的策略
- 尽量有效使用寄存器
- 在存储系统中使用页访问模式。
- 分析cache的行为
- 指令冲突通过重写代码、重新调度来处理
- 对标量数据冲突,移动数据
- 数组数据冲突,考虑移动数据或填充
程序级功耗分析与优化
- 指令
- 指令的选择;
- 指令的顺序;
- 操作码和操作数位置;
- 内存系统的设计–能换来最大的节能效益
寄存器能耗最低,其次Cache,最差内存
- cache的设计
- cache太小,内存开销大,程序运行慢,系统功耗大
- cache太大,功耗很高却没有带来相应的性能的提高
- 总体建议:
高性能=低功耗
- 程序运行更快也能降低功耗
- 每个操作的相对功耗(Catthoor et al)
操作 | 相对功耗 |
---|---|
memory transfer | 33 |
external I/O | 10 |
SRAM write | 9 |
SRAM read | 4.4 |
multiply | 3.6 |
add | 1 |
程序验证与测试
- 关注功能验证
- 主要策略:
- 黑盒测试:无法了解程序内部结构
- 白盒测试:以程序内部结构为基础
白盒测试
- 检查源代码,以确定是否正常工作:
- 你能实际执行一条路径吗?
- 沿该路径能够获得你想要的值吗?
- 测试过程:
- 可控的:给程序提供输入;
- 执行程序进行测试;
- 可观察性:检查输出。
执行路径与测试
- 在功能测试和性能测试中,路径非常重要;
- 一个程序的路径通常是指数级的;
- 一些路径是控制其它路径的;
- 路径的执行测试了程序的控制和数据两方面。
- 选择测试路径的方法
- 执行每条语句至少一次(语句覆盖)
- 执行每个分支至少一次(分支覆盖)
- …
- 环路复杂度(Cyclomatic complexity)
- 利用环路复杂度可以测量程序的控制复杂度
- e = # edges
- n = # nodes
- p = 组件的个数,
- 结构化程序:1
M = e – n + 2p.
- M为2元判决的数目(>=3)加1,和高元分支减1.
-
分支测试(branch testing)
- 分支测试的策略:
- 每个条件做假和真的测试
- 每个简单的条件至少测试一次
- 分支测试的策略:
-
数据流测试——域测试
- 用于对不等式测试
- 例如:对j<=i+1测试
- 三个测试点
- 2个有效区域的边界上;
- 一个区域外,但介于另外2个点的i之间
- 三个测试点
-
定义-使用对
- 当一个变量被赋值,就说
定义
了 - 当该变量出现在赋值表达式的右手,则说被
使用
了 - 数据流测试对选定的定义-使用对进行测试
- 在定义时赋予一个特定值
- 观察使用点
- 确保得到期望值
- 当一个变量被赋值,就说
-
循环测试应当测试的几种重要情况:
- 整体跳过循环;
- 一次循环迭代;
- 两次循环迭代;
- 若循环迭代有一个上限,大大低于最大迭代次数的一个值;
- 接近循环迭代上限的一个值。
黑盒测试
- 若完全白盒测试,需要大量的测试,
- 需要其它的测试方法-黑盒测试
- 随机测试
- 可以基于软件规范的权重分布的随机测试;
- 回归测试
- 基于以前版本的测试;
- 可能以前版本是白盒测试;
- 随机测试
- 测试多少是足够的?
- 耗尽测试是不实际的;
- bug在一定的范围内;
- 好的组织测试,可以使bug报告率在一个非常低的范围内;
- 错误注入:
- 添加已知的错误;
- 运行测试;
- 判断有多少百分率的bug被发现。