代码写多了,程序就会变得臃肿;程序臃肿了,就会变慢。 这时提高代码执行效率就非常重要了。 但是,代码优化并不是几条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的散列算法不可小觑啊。
阅读全文 »Wordpress的效率问题似乎一直是议论的热点,今天来看看其中一条SQL语句的效率。
首先将所有插件禁用,并切换为默认模板。 然后按照《WordPress执行效率问题》这篇文章的方法, 在 wp-config.php 中加入
<?php define('SAVEQUERIES', true); ?>
在模板的 footer.php 中加入
<!-- <?php var_dump($wpdb->queries); ?> -->
访问首页,即可从源代码中看到SQL语句。
执行时间最长的是这一条,花了0.02秒:
SELECT DISTINCT * FROM wp_posts
WHERE 1=1
AND post_date_gmt <= '2007-01-02 07:12:59'
AND (
post_status = 'publish'
OR
post_author = 1
AND post_status != 'draft'
AND post_status != 'static'
)
AND post_status != 'attachment'
GROUP BY wp_posts.ID
ORDER BY post_date DESC
LIMIT 0, 10"
这条语句位于 class.php 的 WP_Query->&get_posts() 函数。
说说这个SQL语句的问题。不妨先来看看 wp_posts 表的结构(省略无关列)。
| 列名 | 类型 | 索引 |
| ID | bigint(20) | PRIMARY |
| post_author | bigint(20) | |
| post_date_gmt | datetime | |
| post_status | enum | INDEX |
| post_name | varchar(200) | INDEX |
首先是关键字 DISTINCT,DISTINCT的作用是去掉重复的数据, 相当于对选择的列进行 GROUP BY。而这条语句中选择了 * 列,包含了主键列 ID, 每行数据必然不相同,因此 DISTINCT 关键字起不到任何作用, 白白浪费一次排序。
其次是 post_date_gmt 和 post_author 上的比较操作。wp_posts表仅有 ID、post_status 和 post_name 列上有索引,对其他列的比较操作使得mysql不得不访问wp_posts表的所有数据, 影响效率。
然后是 post_status 上的不等于的比较。对索引列使用不等于比较, 会导致数据库不能使用索引(索引只能判断等于关系)。 另外显然已经有了 post_status = 'publish' 的条件, 不必再判断 post_status != 'attachment'。
最后是 GROUP BY wp_posts.ID,仍然是对一个不会重复的列进行 GROUP BY, 毫无意义。
如果不考虑功能损失,将这个 SQL 语句优化为下面这样,则仅需花费0.003秒。
SELECT * FROM wp_posts WHERE 1=1 AND post_status = 'publish' ORDER BY post_date DESC LIMIT 0, 10"
使用Apache Bench做测试,优化前平均每个请求的处理时间为 503.125ms,优化后为 478.125ms。
