使用Devel::Timer识别慢代码

程序速度是编程中的一个重要因素。没有人希望自己的程序执行速度变慢。作为一种通用编程语言,Perl通常足够快,可以处理大多数事情,如果不快,我们有一些优秀的工具可以帮助我们使其更快。我们可以使用Benchmark模块比较代码和Devel::NYTProf来产生我们程序的详细分析。

本文介绍了Devel::Timer,这是一个我喜欢在优化现有子程序时使用的模块,并且不确定子程序中每个语句的执行时间。它很容易设置,所以如果你以前没有使用过它,一旦你阅读了这篇文章,你将拥有另一个优化代码的工具。

使用Devel::Timer获取时间报告

假设我有一个非常慢的子程序,但我不确定是什么让它变慢。这个(虚构的)子程序看起来像这样

sub foo {
  my $args = shift;

  die 'foo() requires an hashref of args'
    unless $args && ref $args eq 'HASH';

  my %parsed_args = validate_args($args);

  my $user        = find_user($parsed_args{username});

  my $location    = get_location($parsed_args{req_address});

  my $bar         = register_request($user, $location);

  return $bar;
}

我可以用Devel::Timer来计时子程序中的每个语句,并告诉我每个语句花费了多少时间

use Devel::Timer;

sub foo {
  my $args = shift;
  my $timer = Devel::Timer->new();

  die 'foo() requires an hashref of args'
    unless $args && ref $args eq 'HASH';
  $timer->mark('check $args is hashref');

  my %parsed_args = validate_args($args);
  $timer->mark('validate_args()');

  my $user        = find_user($parsed_args{username});
  $timer->mark('find_user()');

  my $location    = get_location($parsed_args{req_address});
  $timer->mark('get_location()');

  my $bar         = register_request($user, $location);
  $timer->mark('register_request()');

  $timer->report();
  return $bar;
}

我已经更新了代码,导入了Devel::Timer并构造了一个新的$timer对象。在我想计时每个语句之后,我调用mark方法,它将一个条目添加到计时器中,就像在秒表中记录分段时间。最后,我调用report,它会打印出一个表格,列出每次调用mark之间花费的时间。

Devel::Timer Report -- Total time: 0.0515 secs
Interval  Time    Percent
----------------------------------------------
02 -> 03  0.0328  63.68%  validate_args() -> find_user()
04 -> 05  0.0095  18.44%  get_location() -> register_request()
03 -> 04  0.0081  15.72%  find_user() -> get_location()
06 -> 07  0.0010   0.19%  register_request() -> END
01 -> 02  0.0001   0.00%  check $args is hashref -> validate_args()
00 -> 01  0.0000   0.00%  INIT -> check $args is hashref

默认情况下,表格按持续时间降序打印。输出显示该子程序执行了515毫秒,从validate_args()标记到find_user()标记的持续时间占了运行时间的63.68%。所以如果我要重构这个子程序,我会从find_user()函数开始。

在我的经验中,这是典型的时间分布。很难找到一个子程序中的每个语句花费相同的时间。通常只有一个或两个原因,如果你能重构那些,子程序的运行时间可以改善一个数量级或更多。

一个常见的陷阱

在使用Devel::Timer时要当心的一点是惰性求值。这是指代码只在实际需要时才执行。有时第一次调用子程序可能很慢,因为对象被创建,缓存初始化或 whatever。但后续调用要快得多。检查这一点的一个简单方法是多次在同一过程中调用子程序,并检查Devel::Timer报告以确认计时。

参考文献


本文最初发布在PerlTricks.com上。

标签

David Farrell

David是一位职业程序员,他经常推文博客关于代码和编程的艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开一个问题或拉取请求来帮助我们。