实验总结分析报告:从系统的角度分析影响程序执行性能的因素
实验总结分析报告:从系统的角度分析影响程序执行性能的因素
1. 精简的Linux系统概念模型
现代计算机基本都是建立在冯·诺依曼体系结构上,由运算器、控制器、存储器、输入设备和输出设备共同组成。
实际运行中,程序和数据都存储于存储器中,运行时按需调入内存。
程序进入CPU执行后,成为一个个进程,操作系统使用分时机制,并发执行多个进程,同时也需要处理进程的输入输出需求。这些功能的实现依赖于操作系统的进程调度、中断和Linux文件系统。
1.1 进程
1.1.1 进程运行
Linux中栈底为高端地址,栈向低地址方向扩展。
进程中的内存通常分为5个段:
- 代码段:存放程序可执行代码
- 数据段:初始化静态/全局变量
- BSS段:未初始化的静态/全局变量
- 堆:用于动态内存分配
- 栈:存放函数内定义的局部变量和函数调用有关的数据
进入CPU的进程,在属于其自身的运行时间片内,利用栈完成其程序执行。
-
函数内运行
进程在同一个函数内执行时,通过保存在栈中属于该函数的局部变量,以及数据段、BSS段中的全局变量获得其需要的数据,少数时候,需要通过这些变量,进一步读取存储器中的内容或进行I/O。 -
函数调用
- 进行函数调用时,将函数参数、返回地址入栈
- 代码区跳转
- 使用函数序言
// 函数序言
push %ebp // 调用者栈帧入栈
mov %esp, %ebp // 使ebp指向被调用者栈帧
sub $N, %esp // 为局部变量预留空间
- 函数返回
- 使用函数后记
- 返回函数调用前继续执行
// 函数后记
mov %ebp, %esp // 释放为局部变量开辟的栈空间
pop %ebp // 恢复ebp,指向调用者栈帧
ret // 返回。同时移动esp,释放存储返回地址的空间
1.1.2 进程调度
进程的调度存在多种算法,例如:先来先服务、短作业优先、时间片轮转、多级反馈队列调度、优先级调度等。
为了方便讨论,假设使用时间片轮转调度算法,则每个进程使用CPU的时间视为平均分配。
1.2 中断
Linux的体系结构分为用户态和内核态。两个状态分别拥有不同的权限。用户态无法访问地址空间大于等于 0xC0000000 的部分。为了实现一些特权操作(系统调用)和其他需要(时钟中断、进程切换等),需要使用中断的机制,从用户态转为内核态,执行完毕之后,恢复用户态,继续执行。

1.2.1 分类
- 硬件中断(外部中断)
- 软件中断(内部中断、异常)
- 故障
- 陷阱
1.3 文件系统
按照文件的组织方式不同,文件可以被分为:
- 一般文件,如:字符流文件
- 特殊文件,如:目录文件、设备文件
对不同文件有不同的读写方式,为了能够统一文件操作方式,提高上层应用的可移植性,在用户空间与系统空间中间加入虚拟文件系统(VFS)。
VFS对用户控件提供统一的调用接口,方便用户使用,对下层实现不同文件的具体操作。
用户只需调用简单的文件操作接口,具体的操作交由VFS及以下的系统调用完成,通过稳定健壮的系统接口,降低文件读写造成的错误。
1.4 举例:读写文件
- 用户态运行的程序触发文件读写系统调用
- 通过软中断-陷阱(Trap)进入内核态,并通过中断向量表查询触发的系统调用
- 通过系统调用读写文件
- 触发中断,返回用户态执行
2. 影响程序性能表现的原因
2.1 程序源码
目标程序:求斐波那契数列
#include <stdio.h>
#include <stdlib.h>
const int MAX_NUM = 40;
int Fibonacci(int n)
{
if (n == 1 || n == 0)
{
return n;
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
void main()
{
printf("Fibonacci Number from 0 to %d is :\n", MAX_NUM);
for (int i = 0; i <= MAX_NUM; i++)
{
printf("%d ", Fibonacci(i));
}
printf("\n");
}
gcc -o f1 Fibonacci.c
2.2 Perf性能优化工具
2.2.1 查看整体情况
使用Perf性能优化工具对程序 f1 进行性能测试,结果如图所示:

2.2.2 查看报告


2.2.3 分析
从分析结果可以得到以下结论:
- Task-clock数值较高:程序更多的时间用于CPU计算,而非I/O。程序属于CPU繁忙型。
- 通过具体报告,可以看出程序中的
Fibonacci()函数占用了绝大部分运行时间,为影响程序执行的首要因素。
- 通过具体报告,可以看出程序中的
- Context-switches: 有一定的进程切换次数。减少进程切换次数,从而降低进程切换带来的额外开销,能够提高程序的效率。
- Page-faults: 发生缺页的次数。缺页会导致操作系统发生缺页异常中断,从存储器获取缺少的页面,而I/O速度远低于CPU计算,因此会影响程序效率。
2.3 改进程序
根据上述分析,应该从降低程序计算步骤、减少执行时间(更少的进程切换次数)、利用空间局部性减少缺页异常发生次数方面,提高程序性能。
改进程序源码如下:
#include <stdio.h>
#include <stdlib.h>
const int MAX_NUM = 40;
int Fibonacci(int n)
{
int dp1 = 0, dp2 = 1;
for (int i = 2; i <= n; i++)
{
int t = dp1;
dp1 = dp2;
dp2 = t + dp2;
}
return n == 0 ? dp1 : dp2;
}
void main()
{
printf("Fibonacci Number from 0 to %d is :\n", MAX_NUM);
for (int i = 0; i <= MAX_NUM; i++)
{
printf("%d ", Fibonacci(i));
}
printf("\n");
}
gcc -o f2 BetterFibonacci.c
改进程序中,使用循环替代了递归,减少 Fibonacci() 的调用次数,在计算中,复用了之前计算的结果,避免了一部分重复计算,降低了程序的计算步骤。
2.4 查看优化后的性能状况



2.5 比较分析
比较两次使用Perf工具分析得到的结果:
- 明显优化后的代码 task-clock 执行时间更短,从
1974ms --> 0.38ms,具有较大提升。 - 同时进程切换次数也降为0。
- 缺页次数没有变化(经过多次试验,缺页次数有波动,整体维持稳定,与优化前区别很小);
3. 总结
对于一个应用程序的性能需要从多个方面分析,比如程序是CPU繁忙型还是IO繁忙型、进程是否频繁切换等。优化程序时,首要抓住最耗时的部分,根据木桶效应,改进该部分能够对程序的整体效能提升起到最大的效果。
浙公网安备 33010602011771号