程序的“听诊器”——性能监视工具
听诊器是一种简单工具 , 却给医生的工作带来了革命:它让内科医生能有效地监控病人的身体 。 性能监视工具(profiler)对程序起着同样的作用 。
你现在用什么工具来研究程序?复杂的分析系统很多 , 既有交互式调试器 , 又有程序动画系统 。 正如CT扫描仪永远代替不了听诊器一样 , 复杂的软件也永远代替不了程序员用来监控程序的最简单工具——性能监视工具 , 我们用它了解程序各部分的执行频率 。
本文先用两种性能监视工具来加速一个小程序(记住真正的目的是说明性能监视工具) 。 后续各节简要介绍性能监视工具的各种用途、非过程语言的性能监视工具 , 以及开发性能监视工具的技术 。
1 计算素数程序P1是个ANSI标准C程序 , 依次打印所有小于1000的素数 。
程序P1
int prime(int n){int i;999for (i = 2; i < n; i++)78022if(n%i == 0)831return 0;168return 1;}main(){int i, n;1n = 1000;1for (i = 2; i <= n; i++)999if (prime(i))168printf("%d\n", i);}
如果整型参数n是素数 , 上述prime函数返回1(真) , 否则返回0 。 这个函数检验2到n-1之间的所有整数 , 看其是否整除n 。 上述main过程用prime子程序来依次检查整数2~1000 , 发现素数就打印出来 。
我像写任何一个C程序那样写好程序P1 , 然后在性能监视选项下进行编译 。 在程序运行之后 , 只要一个简单的命令就生成了前面所示的列表 。 (我稍微改变了一些输出的格式 。 )每行左侧的数由性能监视工具生成 , 用于说明相应的行执行了多少次 。 例如 , main函数调用了1次 , 其中测试了999个整数 , 找出了168个素数 。 函数prime被调用了999次 , 其中168次返回1 , 另外831次返回0(快速验证:168+ 831=999) 。 prime函数共测试了78022个可能的因子 , 或者说为了确定素数性 , 对每个整数检查了大约78个因子 。
程序P1是正确的 , 但是很慢 。 在VAX-11/750上 , 计算出小于1000的所有素数约需几秒钟 , 但计算出小于10 000的所有素数却需要3分钟 。 对这些计算的性能监视表明 , 大多数时间花在了测试因子上 。 因而下一个程序只对n考虑不超过\sqrt n 的可能的整数因子 。 整型函数root先把整型参数转换成浮点型 , 然后调用库函数sqrt , 最后再把浮点型结果转换回整型 。 程序P2包含两个旧函数和这个新函数root 。
程序P2
int root(int n)5456{ return (int) sqrt((float) n);}int prime(int n){int i;999for (i = 2; i < = root(n); i++)5288if (n % i == 0)831return 0;168return 1;}main(){int i, n;1n = 1000;1for (i = 2; i < = n; i++)999if (prime(i))168printf("%d\n", i);}
修改显然是有效的:程序P2的行计数显示 , 只测试了5288个因子(程序P1的1/14) , 总共调用了5456次root(测试了5288次整除性 , 168次由于i超出了root(n)而终止循环) 。 不过 , 虽然计数大大减少了 , 但是程序P2运行了5.8秒 , 而程序P1只运行了2.4秒(本节末尾的表中含有运行时间的更多细节) 。 这说明什么呢?
迄今为止 , 我们只看到了行计数(line-count)性能监视 。 过程时间(procedure-time)性能监视给出了较少的控制流细节 , 但更多地揭示了CPU时间:
%timecumsecs#callms/callname82.74.77_sqrt4.55.039990.26_prime4.35.2854560.05_root2.65.43_frexp1.45.51_ _doprnt1.25.57_write0.95.63mcount0.65.66_creat0.65.69_printf0.45.72125.00_main0.35.73_close0.35.75_exit0.35.77_isatty
过程按照运行时间递减的顺序列出 。 时间上既显示出总秒数 , 也显示出占总时间的百分比 。 编译后记录下源程序中main、prime和root这3个过程的调用次数 。 再次看到这几个计数是令人鼓舞的 。 其他过程没有性能监视的库函数 , 完成各种输入/输出和清理维护工作 。 第4列说明了带语句计数的所有函数每次调用的平均毫秒数 。
过程时间性能监视说明 , sqrt占用CPU时间的最多:该函数共被调用5456次 , for循环的每次测试都要调用一次sqrt 。 程序P3通过把sqrt调用移到循环之外 , 使得在prime的每次调用中只调用一次费时的sqrt过程 。
程序P3
int prime(int n){int i, bound;999bound = root(n);999for (i = 2; i < = bound; i++)5288if (n % i == 0)831return 0;168return 1;}
当n=1000时 , 程序P3的运行速度大约是程序P2的4倍 , 而当n= 100 000时则超过10倍 。 以n= 100 000的情形为例 , 过程时间性能监视显示 , sqrt占用了程序P2的88%的运行时间 , 但是只占用了程序P3的48%的运行时间 。 这好多了 , 但仍然是循环的累赘 。
程序P4合并了另外两个加速措施 。 首先 , 程序P4通过对被2、3和5整除的特殊检验 , 避免了近3/4的开方运算 。 语句计数表明 , 被2整除的性质大约把一半的输入归入合数 , 被3整除把剩余输入的1/3归入合数 , 被5整除再把剩下的这些数的1/5归入合数 。 其次 , 只考虑奇数作为可能的因子 , 在剩余的数中避免了大约一半的整除检验 。 它比程序P3大约快两倍 , 但是也比P3的错误更多 。 下面是(带错的)程序P4 , 你能通过检查语句计数看出问题吗?
- 智能手机市场|华为再拿第一!27%的份额领跑全行业,苹果8%排在第四名!
- 空调|让格力、海尔都担忧,中国取暖“新潮物”强势来袭,空调将成闲置品?
- 会员|美容院使用会员管理软件给顾客更好的消费体验!
- 同比|亚马逊公布“剁手节”创纪录战绩:第三方卖家全球销售额超48亿美元 同比大增60%
- 行业|现在行业内客服托管费用是怎么算的
- 闲鱼|电诉宝:“闲鱼”网络欺诈成用户投诉热点 Q3获“不建议下单”评级
- 人民币|天猫国际新增“服务大类”,知舟集团提醒入驻这些类目的要注意
- 产业|前瞻生鲜电商产业全球周报第67期:发力社区团购!京东内部筹划“京东优选”
- 国外|坐拥77件专利,打破国外的垄断,造出中国最先进的家电芯片
- 程序|2020全景生态流量秋季大报告:TOP100APP超半数布局小程序,全景流量重塑行业竞争新格局