第二十章 Perl 调试器

首先,你试着用过 use warinings 用法?

如果你运行 Perl 的时候使用了 -d 开关,那么你的程序将在 Perl 的调试器里 运行。它运行得象一个交互式的 Perl 环境,给你提示输入调试器命令,这样你就 可以检查你的源码,设置断点,输出你的函数调用堆栈,修改变量值等等。任何 调试器不能识别的命令都会当作正在被调试的包里面的 Perl 代码直接执行(用 eval)。(调试器使用 DB 包存储自己的状态信息,以避免破坏你的程序的状态。) 这个功能非常好用,以至于有时候人们经常使用调试器来交互地测试 Perl 的构造。 这时候,它不在乎你让 Perl 调试什么程序,所以我们经常选择一个没有什么意义的:

   %perl -de 42
在 Perl 里,调试器不是一个与被调试的程序完全分离的程序,通常在一些典型的 编程环境中都是这样的。与之不同的是,-d 标志告诉编译器向它准备交给解释器的 分析树里插入源码信息。这就意味着在编译器工作之前,你的程序必须首先正确 编译,如果成功,解释器预先装载一个包含调试器本身的特殊 Perl 库文件。
   %perl -d /path/to/program
该程序会在第一个运行时可执行语句前面停止(参阅有关编译时语句的“使用调试器” 一节)然后询问你输入调试器命令。当调试器停止并且显示你的一行代码的时候,它 显示的是准备执行的代码,而不是刚才执行过的。

当调试器看到一行,他首先检查有没有断点,打印该行(调试器应该处于跟踪模式), 执行任何动作(用 a 命令创建,稍后在“调试器命令”里介绍),最后如果有断点 或者调试器处于单步模式则提示用户。如果没有断点,它象平时一样计算该行然后 继续下一行。

20.1 使用调试器

调试器的提示符是象下面这样的东西:

   DB<8>
或者是:
   DB<<17>>
这里面的数字显示你执行了多少命令。一个类似 csh 的历史机制可以让你通过数字 访问前面的命令。比如 17 将重复命令数为 17 的命令。尖括号的数目表示调试器的 深度。比如,如果你已经在一个断点了,然后又打印一个函数调用的结果,而且那个 函数里面也有一个断点,那么你就会看到多于一对尖括号。

如果你想输入多行命令,比如带几个语句的子过程定义什么的,你可以用反斜杠逃逸 那些通常会终止调试器的新行。下面是一个例子:

   DB<1> for (1..3) {         cont:   print "ok\n";      cont: }
   ok
   ok
   ok

假设你想在你的一个小程序上运行调试器(假设就叫 camel_flea),然后一碰到一个 叫 infested 的函数就停下。下面就是方法:

% perl -d camel_flea

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(camel_flea:2):     pests('bactrian', 4);
  DB<1>

调试器在第一个运行时可执行语句(请参考后文看看编译时语句的问题)之前停止你的 程序运行,然后让你输入一条命令。同时,调试器停下来显示一行代码,它显示的是将 要执行的一行,而不是刚刚执行完的那行。显示出的这行程序可能和你的源文件里的看 上去不完全一样,尤其是你用预处理器对它进行过处理之后。

现在,你想让你的程序一到达 infested 函数就停止下来,所以你可以在那里设立 一个断点,象这样:

   DB<1>b infested
   DB<2>c

现在调试器继续运行直到它到达该函数,在该点,调试器说:

DB<1> b infested
DB<2> c

要开一个“窗口”看看断点周围的源程序,你可以使用 w 命令:

DB<2> w
5     }
6
7       sub infested {
8==>b       my $bugs = int rand(3);
9:          our $Master;
10:         contaminate($Master);
11:         warn "needs wash"
12              if $Master && $Master->isa("Human");
13
14:         print "got $bugs\n";

DB<2>

当你看到 ==> 标记的时候,你的当前行是第八行,并且在那里的 b 告诉你在那个 地方有一个断点。如果你已经设置了一个动作,那么那里应该也有一个 a。行号里带 冒号的行是可以中断的;其他的则不行。

想看看是谁调用的谁,你可以用 T 命令看看堆栈的回朔跟踪:

DB<2> T
$ = main::infested called from file `Ambulation.pm' line 4
@ = Ambulation::legs(1, 2, 3, 4) called from file `camel_flea' line 5
. = main::pests('bactrian', 4) called from file `camel_flea' line 2
---

开头的字符($,@ 或者 .)告诉你该函数分别是在标量环境,列表环境,还是空环境 里调用的。这里有三行是因为当你运行堆栈回朔的时候你已经在三层函数深度了。 下面是每一行的含义:

如果你有编译阶段的执行语句,比如在 BEGIN 和 CHECK 块里的代码或者 use 语句,它们通常不能被调试器停止下来,不过 require 和 INIT 块可以,因为它们发生在向运行阶段转换的过程中(参阅第十八章,编译)。编译阶段的语句可以通过在 PERLDB_OPTS 里设置 AutoTrace? 选项来跟踪。

你可以在你的 Perl 程序本身内部对 Perl 调试器施加一些影响。比如,你可以这么做:当某个程序在调试器里运行时,在某个子过程里设置一个自动的断点。不过,在你的程序里,你可以用下面的语句把控制交还给调试器,如果调试器没有运行,它是无害的:

   $DB::single = 1;

如果你把 $DB::single 设置为 2,则等效于 n 命令,而值为 1 模拟 s 命令。 $DB::trace 变量应该设置为 1 以模拟 t 命令。

另外一个调试模块的方法是在装载的时候设置断点:

   DB<7>b load c:/perl/lib/Carp.pm
   Will stop on load of `c:/perl/lib/Carp.pm'.

然后用 R 命令重新起动调试器。想获得更好的控制,你可以用 b compile subname 命令,这样在某个子过程完成编译以后马上停止。

20.2 调试器命令

当你向调试器里键入命令的时候,你不需要用分号来结束他们。用反斜杠来接续一行 (但只是在调试器里)。

因为调试器使用 eval 执行命令,一旦命令返回,my,our,和 local 设置都会 消失。如果某条调试器命令和你自己的程序里的某个函数一样,那你只需要在那个 函数调用前面加上任何让它看上去不象调试器命令的东西就行了,比如一个前导的 ; 或者一个 +。

如果一个调试器内建命令滚过了你的屏幕,那么你只需要在该命令的前面放一个前导 的管道符号就可以了,这样它就会运行你的分页器:

   DB<1> |h

调试器有很多命令,我们把它们分成(有些武断)步进和运行,断点,跟踪,显示, 代码定位,自动命令执行,当然还有杂项几类。

可能最重要的命令是 h,它提供帮助。如果你在调试器提示符上键入 h h,你能得到 一个精简的帮助列表,刚好一页大小,是我们特地设计成那样的。如果你键入命令 h COMMAND,你得到的是那条调试器命令的帮助。

20.2.1 部件和运行

调试器一行一行地运行你的程序。下面的命令让你控制什么时候跳过,什么时候停止。

   s 调试器命令单步运行程序。也就是说,调试器将执行你的下一行程序,直到
   到达另外一个语句,必要时步进到子过程里。如果要执行的下一行涉及函数
   调用,那么调试器将在那个函数的第一行停止。如果使用的 EXPR 里包含一个
   函数调用,那么它也会单步运行。

   n 命令执行子过程调用,而不用单步进入它们,直到抵达同层(或者更高层)
   的下一条语句的开头时停止。如果使用的 EXPR 里包含函数调用,那么会执行
   那些函数然后在每个语句前面停下来。

   如果你只是在调试器提示符上敲回车,那么重复执行前面一条 n 或者 s 
   命令。

20.2.2. 断点

调试器命令 b 在 LINE 之前设置一个断点,告诉调试器在该点停止程序, 这样你就可以检查一番。 如果省略了 LINE,则在将要执行的行上设置断点。 如果声明了 CONDITION,那么每次到达该 语句的时候都会计算它:只有 CONDITION 为真的时候才会触发一个断点。你只能在一个开始一个执行语句 的行上设置断点。请注意这里的条件不用 if:

      b 237   $x > 30
      b 237   ++$count237 < 11
      b 33   /pattern/i

有好几种在还没有编译的代码上设置断点的方法。b postpone 形式在编译完 SUBNAME 以后,在它的第一行设置一个(可能是有条件的)断点。

20.2.3 跟踪

20.2.4 显示

      V Pet::Camel SPOT FIDO

在变量名字 VARS 的位置,你可以用 ~PATTERN 或者 PATTERN 打印一个现有的变量,该变量名与声明的模式匹配或者不匹配。

20.2.5 定位代码

在调试器里,你可以用下面的命令抽取和显示你的部分程序。

20.2.6 动作和命令执行

在调试器里,你可以声明在特定时间里要做的动作。你也可以运行内部程序。

这条命令设置一个在执行第 LINE 行程序之前的动作,如果省略了 LINE, 则是当前行。比如,下面这条命令每当到达第 53 行的时候就打印出 $foo:

      a 53 print "DB FOUND $foo\n"

如果没有声明 COMMAND,则在指定的 LINE 处的动作被删除。如果既没有 LINE,也没有 COMMAND,则在当前行处的动作被删除。

      DB<1> !!who | more

DB<1> sub saywho { print "Users: ", `who` } DB<2> || saywho()

20.2.7 杂项命令

在那些通常没有 man 工具的系统上,调试器调用 perldoc;如果你想修改 这个性质,把 $DB::doccmd 设置为你喜欢的阅读器。这些设置可以放在一个 rc 文件里或者用直接赋值的方法来实现。

DB<1> O OPTION='this isn\'t bad'
               OPTION = 'this isn\'t bad'

DB<2> O OPTION="She said, \" Isn't it?\""
               OPTION = 'She said, "Isn\'t it?"'

由于历史原因,=VALUE 是可选的,但是只是对那些可以为 1 的才缺省为

  1. --也就是说,那些布尔选项。最好给用 = 给选项赋一个特定的值 VALUE。 OPTION 可以缩写,不过除非你想在内部保密,否则不应该这么干。你可以 一次设置好几个选项,参阅“调试器选项”获取选项列表。

20.3 客户化调试器

调试器包含很多配置挂钩,你可能永远不用自己修改这些东西。你可以在调试器里面 用 O 命令修改调试器的行为,也可以在命令行上通过 PERLDB_OPTS 环境变量来做, 或者是用存放在 rc 文件里的预设命令来实现。

20.3.1 调试器的编辑器支持

调试器的命令行历史机制不象许多 shell 那样提供命令行编辑功能:你不能用 ^p 检索以前的行,或者用 ^a 移动到行的开头,尽管你可以用类似与 shell 用户的叹号 来执行前面的行。不过,如果你安装了 CPAN 里的 Term::ReadKey 和 Term::ReadLine 模块,你就拥有了类似 GNU readline(3) 提供的所有功能。

如果你在你的系统上安装了 emacs, 它可以与 Perl 调试器交互地工作,这样就给 你提供一个类似它给 C 调试器提供的那样的集成开发环境。Perl 带着一个启动文件, 可以令 emacs 能够理解 Perl 的语法,表现得象一个面向语法的编辑器。请查看一下 Perl 源程序目录的emacs 目录。使用 vi 的用户也可以看看 vim (和 gvim,用得 最多的版本),获取 Perl 关键字的彩色显示。

还有一个我们做的(Tom)类似的设置文件,可以用于任何厂商提供的 vi 和 X11 窗口系统。它运行起来很象 emacs 提供的多窗口支持,它是调试器驱动编译器。 不过,在我们写这些的时候,它在 Perl 发布的最终位置还不明确。不过我们认为你 应该知道这是可能的。

20.3.2 用 init 文件进行客户化

你可以通过设置 .perldb 或者 perldb.ini 文件(用哪个取决于你的操作系统)来 做一些客户化工作。该文件里包含初始化代码。这个初始化文件包含的是 Perl 代码, 不是调试器命令,并且是在查看 PERLDB_OPTS 环境变量之前处理。比如,你可以用 下面的方法在 %DB::alias 散列里增加记录来制作别名:

   $alias{len} = 's/^len(.*)/p length($1)/';
   $alias{stop} = 's/^stop (at|in) /b/';
   $alias{ps} = 's/^ps\b/p scalar /';
   $alias{quit} = 's/^quit(\s*)/exit/';
   $alias{help} = 's/^help\s*$/|h';

你可以在你的初始化文件里使用函数调用调试器内部的 API 修改那些选项:

   parse_options("NonStop=1 LineInfo=db.out AutoTrace=1 frame=2");

如果你的初始化文件定义了子过程 afterinit,则该函数是在调试器初始化结束以后 调用的。init 文件可以位于当前目录或者家目录。因为这个文件包含任何 Perl 命令,出于安全原因,它必须为超级用户或者当前用户所有,并且只有它的所有者有 写权限。

如果你想修改调试器,把 Perl 库目录的 perl5db.pl 拷贝成另外一个名字然后把它 修改为你的所需。然后你就需要把你的 PERL5DB 环境变量设置为下面这样的东西:

   BEGIN {require "myperl5db.pl" }

最后,你可以用 PERL5DB 直接设置内部变量或者调用内部调试器函数来客户化 调试器。不过,要小心的是,任何没有在这里或联机的 perldebug,perldebuguts, 或者 DB 手册页里提到的变量和函数都被认为是只用于内部目的并且容易在不知觉的 情况下被修改。

20.3.3 调试器选项

调试器有无数的选项,你可以用 O 命令设置它们,可以通过交互地调用 O 命令或者从环境变量设置或者用一个 init 文件设置。

       recallCommand, ShellBang

用语回忆一条命令或者派生一个 shell 的字符。缺省时,两个都设置为 !。

要关闭这个缺省的安全模式,把这些值设置得比 0 大。在级别 1,你得到 包括任何类型的警告(通常很讨厌)或例外(通常很有用)的回朔。糟糕的 是,调试器无法区分致命错误和非致命错误。如果 dieLevel 为 1,那么 调试器还会跟踪你的非致命例外,并且如果它们来自 eval 的字串或者来自 你准备装载的模块里面的任何 eval,那么它们会被调试器随意修改。如果 dieLevel 为 2,则调试器不关心它们来自何方:它篡夺你的例外句柄然后 打印一个回朔,然后用它自己的方法修改所有例外。这个特性可能对某些跟踪 用途有用,不过可能会把任何认真对待例外处理的程序搞得混乱不堪。

如果调试器捕获到任何 INT,BUS,或者 SEGV 信号,那么它会试图打印一条 消息。如果你正在一个慢速的系统调用里(比如 wait 或者 accept,或者从 你的键盘或者套接字里的 read),而且没有设置你自己的 $SIG{INT} 句柄, 那么你就无法 Control-C 退回到调试器里,因为调试器自己的 $SIG{INT} 句柄并不知道它要抛出一个例外以 longjmp(3) 跳出慢速的系统调用。

如果 frame & 4,打印函数的参数,以及环境和调用者信息。如果 frame & 8,那么打印参数里也会有重载的 stringify 和 tie 过了的 FETCH。如果 frame & 16,还打印子过程的返回值。

参数列表的截断长度由下一个选项控制。

下面的选项影响 V,X 和 x 命令的使用:

20.4 不被注意的执行

在启动的过程中,选项是从 $ENV{PERLDB_OPTS} 里初始化的。你可以在那里放上 初始化选项 TTY,noTTY,ReadLine,和 NonStop?

如果你的初始化文件包含:

   parse_options( "NonStop=1 LineInfo=tperl.out AutoTrace");

那么你的程序就会在没有人的干涉的情况下运行,把跟踪信息放到 db.out 文件里。 (如果你中断了调试过程,如果你还想看见什么东西的话,那你最好把 LineInfo? 重新设置为 /dev/tty。)

下面的选项只能在启动的时候声明。如果你要在初始化文件里设置它们,调用 parse_options("OPT=VAL").

这个模块应该实现一个叫 new 的方法,它返回带两个方法的对象:IN 和 OUT。这些方法应该为调试器返回文件句柄分别用于输入和输出。 new 方法 在启动的时候应该检查包含 $ENV{PERLDB_NOTTY} 的参数,或者是 "/tmp/perldbtty$$"。调试器并没有检查这个文件的所有权和是否放开了 写权限,所以理论上是有安全风险的。

有时候选项可以用第一个字母的缩写唯一地标识,不过我们建议你使用全称,主要是 为易读性和将来的兼容性考虑。

下面是一个利用 PERLDB_OPTS 环境变量自动设置选项的例子。(注:我们这里用的 是 sh 语法来设置环境变量。使用其他 shell 的读者应该做相应的调整。)这个例子 以非交互方式运行你的程序,为每个子过程的入口和执行的每一行打印一条信息。 调试器的跟踪输出放到文件 tperl.out。这样就可以让你的程序仍然使用正常的标准 输入和输出,而不用担心在中间混杂有跟踪信息。

$ PERLDB_OPTS="NonStop frame=1 AutoTrace? LineInfo?=tperl.out" perl -d myprog

如果你中断了该程序,你需要循序重置为 O LineInfo?=/dev/tty 或者是你的平台相关的设备。否则,你将看不到调试器的提示符。

调试器支持

Perl 为在编译器或者运行时创建象标准调试器这样的调试环境提供了特殊的调试 挂钩(hook)。不过不要把这些挂钩和 perl -D 选项混淆,-D 选项只有在你的 Perl 是带 -DDEBUGGING 支持编译的时候才可以用。

比如,当你从 DB 包里调用 Perl 内建的 caller 函数的时候,调用时对应的堆栈桢 被拷贝到 @DB::args 数组。当你带着 -d 开关调用 Perl 时,同时还打开了下面这 几项附加的特性:

请注意,如果 &DB::sub 需要需要外部数据来允许,那么在它完成之前,不可能有 其他子过程调用。就标准调试器而言,$DB::deep 变量(你在强制的中断之前最多 可以递归调用调试器的深度)给出了这么一个依赖性的例子。

20.5.1 书写你自己的调试器

最小的可以用的调试器包含一行:

sub DB::DB {}

这行代码因为没有做任何事情,因此可以很容易地通过 PERL5DB 环境变量定义:

   % PERL5DB="sub DB::DB {}" perl -d your-program
另外一个微型调试器,稍微有点用,可以象下面这样创建:
   sub DB::DB {print ++$i; scalar }

这个小调试器将在每个它碰到的语句前面打印一个序列数,然后在继续之前等待你输入一个回车。

下面的调试器,尽管看起来很小,但是相当有用:

   {
      package DB;
      sub DB {}
      sub sub {print ++$i, " $sub\n"; &$sub}
   }

它打印子过程调用的序号和子过程名字。请注意 &DB::sub 必须从 DB 包中编译,就象我们这里做的那样。

如果你以现有的调试器做你的新调试器,那么有一些挂钩可以帮助你客户化它。在启动的时候,调试器从当前目录或者你的家目录读取你的 init 文件。读完文件之后,调试器读取 PERLDB_OPTS 环境变量然后把它们当作一个 O ... 行的剩余部分——就象你可能在调试器提示符键入的那样。

调试器还维护内部特殊变量,比如 @DB::dbline,%DB::dbline,它们是 @{":::_<current_file"} %{"::_<current_file"} 的别名。这里 current_file 是当前选择的文件,可能是用调试器的 f 命令明确选择的或者是执行流隐含地选择的。

有些函数也可以帮助你客户化。DB::parse_options(STRING) 把一行象 O 选项那样 分析。DB::dump_trace(SKIP[, COUNT]) 忽略里面声明的数目的(堆栈)桢并且 返回包含有关调用桢的信息列表。(如果没有 COUNT,就是所有桢)。每条记录都是 一个指向散列的引用,这个散列有有键字“context”(可以是 ., $, 或者 @), “sub”(子过程名或者有关 eval 的信息),“args”(undef 或者一个指向一个 数组的引用),“file”,和“line”。DB::print_TRACE(FH, SKIP[, COUNT[, short]]) 向你提供的文件句柄打印有关调用桢的格式化信息。最后两个函数可以方便 地作为调试器的 < 和 << 命令的参数。

你用不着学习所有的这些东西——我们中的大多数都用不着。实际上,如果我们需要 调试一个程序,我们通常只是在程序里的一些位置插入几条 print 语句,然后再运行 程序就可以了。

更好的情况下,我们甚至可以先打开警告。通常这样就足以定位问题的所在,这样就 可以省下许多工作。就算这样也无法找出错误,我们还可以告诉你除了 -d 开关以外, 还有一个很不错的调试器可以为你缝补几乎所有漏洞——除了不能替你找错误以外。

不过如果你想记住有关客户化调试器的某件事情的话,那么这件事情可能是:不要把 臭虫局限于那些让 Perl 不正常的东西。如果你的程序让你觉得难受,那么这也是 一只臭虫。早些时候,我们给你显示了几个非常简单的客户化调试器。在下一节里, 我们将向你演示另外一种客户化调试器的例子,一个可能会(或者也可能不会)帮助 你调试那些被认为是“这东西有完没完?”的臭虫。

20.6 Perl 调节器

你想让你的程序运行得快些吗?你当然想。不过,你首先得停下来问问自己,“我真的 需要花时间让这东西跑的快些吗?”休闲性的优化是比较有趣的工作,(注: Nathan Torkington 说的,这节是他写的。)不过通常你有使用你的时间的更好的 地方。有时候你只需要预先计划好然后当你去喝茶的时候再运行程序。(或者把运行 程序当作去喝茶的借口。)但是如果你的程序绝对需要运行得更快,那你就要开始调节 它了。调节器可以告诉你你的程序哪一部分花了最多的时间运行,这样你就不用花太多 时间在优化一个对总运行时间作用很小的子过程上。

Perl 带着一个调节器,Devel::DProf 模块。你可以键入下面的命令调节在 mycode.pl 里的 Perl 程序:

   perl -d:DProf mycode.pl

虽然我们管它叫调节器——它的作用就是调节器,但 DProf 采用的机制和我们在本章 早些时候讨论的内容完全一样。DProf 只是一个记录 Perl 进入和离开每个子过程的 时间的调试器。

当你调节的脚本结束的时候,DProf 会向一个叫 tmon.out 的文件倾倒计时信息。和 Perl 一起发布的 dprofpp 程序知道如何分析 tmon.out 以及产生输出。你还可以把带 -p 开关的 dprofpp 用做整个处理过程的前端(见稍后的描述)。

给出下面程序:

   outer();
   
   sub outer {
      for (my $i=0; $i < 100; $i++) { inner () }
   }

   sub inner {
      my $total = 0;
      for (my $i=0; $i < 1000; $i++) { $total += $i }
   }

   inner();

dprofpp 的输出是:

Total Elapsed Time = 0.537654 Seconds
  User+System Time = 0.317552 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 85.0   0.270  0.269    101   0.0027 0.0027  main::inner
 2.83   0.009  0.279      1   0.0094 0.2788  main::outer

请注意这里的百分数加起来不等于 100。实际上,在这个例子里,它们离 100 差得 很远,这样就给你一个暗示说你应该运行这段程序时间长一点。一个通用的规则就是, 你能收集到的调节数据越多,你的得到的采样统计就越好。如果我们把外层循环增加到 运行 1000 次而不是 100 次,我们就能得到更准确的结果:

Total Elapsed Time = 2.875946 Seconds
  User+System Time = 2.855946 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 99.3   2.838  2.834   1001   0.0028 0.0028  main::inner
 0.14   0.004  2.828      1   0.0040 2.8280  main::outer

第一行告诉你程序从开始到结束运行了多长时间。第二行显示了两个不同数据的总和: 花在执行你的代码(“用户”)的时间和花在你的代码做的系统调用(“系统”)里的 时间。(在这里我们必须容忍一点点精度上的错误——计算机的时钟几乎肯定不是以 百万分之一秒记的。运气好的话可能是以百分之一秒记。)

你可以用 dprofpp 的命令行选项修改“用户+系统”时间。-r 显示总时间,-s 只 显示系统时间,而 -u 只显示用户时间。

剩下的报告就分解成每个子过程运行的时间。“Exclusive Time”行表示当子过程 outer 调用子过程 inner 的时候,花在 inner 上的时间没有算做 outer 花的时间。 要改变这个模式,让 inner 花的时间算在 outer 花的时间之内,给 dropp 一个 -I 选项。

对每个子过程,都报告如下内容:%Time,在这个子过程调用花的时间的百分比; ExclSec?,在这个子过程上花的时间(以秒计,不包括它里面调用子过程花的时间); CumulS?,在这个子过程上花的时间(以秒计,包括它里面调用子过程花的时间); #Calls,调用该子过程的次数;sec/call,平均每次调用该子过程花费的时间(以秒 计,不包括它里面调用子过程花的时间);Csec/c,平均每次调用该子过程花费的时间 (以秒计,包括它里面调用子过程花的时间);

当然,最有用的数据是 %Time,它告诉你你的时间花在什么地方了。在我们的例子里, inner 子过程花的时间最多,所以我们可以尝试优化该子过程,或者找一个能够减少 调用它的算法。:-)

dprofpp 的选项给你提供了访问其他信息的机会,或者修改时间计算的方法。你还 可以让 dprofpp 先替你运行脚本,这样你就用不着老是记着用 -d:DProf 开关:

      main::inner x 1      0.008s
      main::outer x 1       0.467s = (0.000 + 0.468)s
      main::inner x 100 0.468s

按照下面的文字理解上面的输出:你的程序的顶级调用了一次 inner,然后它运行了 0.008 秒,然后顶级调用了一次 outer,而它运行了 0.467(0秒 outer 本身,0.468秒outer里的子过程调用)秒,包括调用 inner 100 次(花了 0.468秒)。明白了?

在同一级的分支里(比如,inner 调用了一次并且 outer 调用了一次)是按照包含的时间排序的。

其他选项在 dprofpp(1),它的标准手册页里描述。

DProf 并不是你的调节器的唯一选择。CPAN 还有 Devel::SmallProf,它可以汇报 你的程序里每一行花费的时间。它可以帮助你找出某些有着奇怪开销的 Perl 构造。 大多数内建函数都相当高效,但是我们很容易不小心写了一个开销随着输入成指数增长 的正则表达式。参阅第二十四章,常用练习,里的“效率”一节获取其他提示。

现在我们可以去喝杯茶了。你得看下一章了。


to top