这一系列的文章参考了这个专题。 我从其中找出一些比较实用的,用中文总结一下,并加上一些我的想法。 懂日文的同学就直接去原文看吧。
阅读全文 »代码写多了,程序就会变得臃肿;程序臃肿了,就会变慢。 这时提高代码执行效率就非常重要了。 但是,代码优化并不是几条best practice就能完成的。 那些无关痛痒的空间分配、减少复制等优化措施,虽然有效,但却微乎其微。 优化的关键,是要找出瓶颈并解决之,这样才能以最小的代价获得最佳的效果。
这就用到Perl的一个强大的工具:DProf。 它可以测定程序执行的每个函数所花费的时间, 通过它,你可以迅速找到瓶颈在什么地方,再对症下药。
最近我做的一次性能分析是这样的。 我们的系统在某种条件下发送邮件时特别慢, 甚至等待十几分钟也无法结束。究竟问题出在哪里却不得而知。 于是DProf上场了。
首先写了个脚本,专门调用发送邮件的功能,排除其他功能的影响。 然后运行命令:
$ perl -d:DProf ./sendmail.pl
运行结束之后,会在当前目录下生成一个tmon.out文件。 使用dprofpp即可查看统计信息:
$ dprofpp Total Elapsed Time = 19.70195 Seconds User+System Time = 18.52195 Seconds Exclusive Times %Time ExclSec CumulS #Calls sec/call Csec/c Name 83.5 15.46 15.770 11 1.4059 1.4337 Foo::Bar::crypt 9.34 1.730 1.730 2 0.8650 0.8650 Net::Cmd::datasend 1.24 0.230 0.305 17459 0.0000 0.0000 Crypt::Blowfish::encrypt 0.70 0.129 0.818 36 0.0036 0.0227 base::import 0.59 0.110 0.709 24 0.0046 0.0296 Foo::Bar::FooBarList::BEGIN 0.44 0.082 0.082 9108 0.0000 0.0000 IO::Wrap::read 0.40 0.075 0.075 17647 0.0000 0.0000 Crypt::Blowfish::crypt 0.27 0.050 0.887 7 0.0071 0.1268 main::BEGIN
统计信息的意思分别是:
- ExclSec: 函数自身的执行时间(不包括函数调用的其他函数)
- CumulS: 函数的执行时间(包括函数调用的其他函数)
- #Calls: 调用次数
- sec/call: ExceSec/#Calls
- Csec/c: CumulS/#Calls
- Name: 函数名称
从上面的统计信息中可以看出,Foo::Bar::crypt占用了最多的时间(83.5%)。 而且更为重要的信息是,CumulS - ExclSec = 15.770 - 15.46 = 0.31, 也就是说,绝大部分时间都耗在了 Foo::Bar::crypt 函数本身,而不是它调用的其他函数。 这样,只需针对Foo::Bar::crypt函数进行分析就可以了。
还可以使用另一个性能测试工具Devel::SmallProf。
那天和人讨论一个简单的应用:集合减法,即给定集合 A 和 B ,计算 C = A - B, 也就是说求属于A但不属于B的元素。集合A和B使用数组来存储,元素仅限于整数。 求计算方法。
Perl自身没有集合运算函数,为了这一个功能也犯不上去安装CPAN模块, 所以我们考虑了以下几个方案:
- 利用foreach作最直白的循环来判断
- 利用grep代替foreach
- 将元素用逗号连接之后用正则表达式匹配
- 将数组转化成散列之后判断
四种方法都能实现,但效率如何?本以为使用foreach应该是最快的, 没想到测试之后发现散列居然是最快的。
Rate use grep use regexp use foreach use hash use grep 359/s -- -58% -63% -90% use regexp 861/s 140% -- -11% -77% use foreach 965/s 169% 12% -- -74% use hash 3769/s 950% 338% 291% --
如果元素不是整数而是随机的字符串,正则表达式的效率会急剧下降,而散列依然能保证领先的地位。 看来Perl的散列算法不可小觑啊。
阅读全文 »Devel::SmallProf 是个很好用的模块,可以方便地测量出代码每一行的执行时间,以便进一步优化。
例如以下程序,文件名为 prof_sample.pl。
#!/usr/bin/perl
my $str = "0";
for (my $i = 0; $i < 100; $i++) {
$str =~ s/\d+/($&+1)/e;
print $str."\n";
}
该程序的功能是输出整数 1 到 100。当然实际写程序时可不要用这么低效率的方法。 安装 Devel::SmallProf 之后我们来测量一下它每一行代码的执行时间。
perl -d:SmallProf prof_sample.pl
执行之后会在当前目录下生成一个 smallprof.out 文件,其内容如下:
================ SmallProf version 1.15 ================
Profile of prof_sample.pl Page 1
=================================================================
count wall tm cpu time line
0 0.000000 0.000000 1:#!/usr/bin/perl
0 0.000000 0.000000 2:
1 0.000006 0.000000 3:my $str = "0";
101 0.006418 0.010000 4:for (my $i = 0; $i < 100; $i++) {
200 0.002581 0.000000 5: $str =~ s/\d+/($&+1)/e;
100 0.001509 0.000000 6: print $str."\n";
1 0.000003 0.000000 7:}
前三列的数字分别为执行次数、消耗时间、消耗CPU时间。
如果你的程序使用 use 语句引用了其他模块,那么所有被引用的程序都将被分析,生成一个长长的报告。这时可以使用下面的命令来迅速找到耗时最长的命令。
sort -k 2nr,2 smallprof.out | less
