分析 PHP 应用程序以查找、诊断和加速运行缓慢的代码(Xdebug+KCacheGrind)

9 篇文章 0 订阅

利用Xdebug+KCacheGrind来调试

原文地址:http://www.ibm.com/developerworks/cn/opensource/os-php-fastapps2/index.html

PHP 的一个较流行的分析器是 Xdebug,它还为交互地调试 PHP 应用程序提供了服务器挂钩(hook)。(参见“调试的更好方法”以了解更多信息。该系列的另一部分将探讨高级交互式调试。) Xdebug 很容易从源代码构建,将其作为 Zend 扩展进行安装也非常简单。(现在已有针对某些平台的二进制文件。)当就绪后,对基于 PHP 页面的每个请求都将生成可在 KCacheGrind 中查看的数据集。

构建并安装 Xdebug

如果具备了 PHP 实用工具 phpize 和 php-config,而且具有对系统的 php.ini 配置文件的访问权,那么安装和设置 Xdebug 只需几分钟的时间。下面给出的指导说明针对 Linux®,不过在 Mac OS X 上的安装步骤实际上与此类似。(您可以从 Xdebug Web 站点找到针对 Microsoft® Windows® 的 Xdebug 预编译版本。)

Xdebug 的最新版本为 V2.0.0RC3(最终版本 V2.0.0 在您阅读此文时也许已经可用)。下载并解包 tarball,然后切换到源代码的子目录。确保phpize 和 php-config 位于 shell 的 PATH,准备使用 phpize 进行构建。

清单 1. 设置 Xdebug
$ wget http://www.xdebug.org/files/xdebug-2.0.0RC3.tgz
$ tar xzf xdebug-2.0.0RC3.tgz
$ cd xdebug-2.0.0RC3/xdebug-2.0.0RC3
$ phpize
Configuring for:
PHP Api Version:         20020918
Zend Module Api No:      20020429
Zend Extension Api No:   20050606

phpize 的产品是一个脚本 —— 名为配置 —— 它对余下的构建过程进行配置。要构建 Xdebug,在 make 后紧接着输入 ./configure 即可。

清单 2. 构建 Xdebug
$ ./configure
checking build system type... i686-apple-darwin8.8.1
checking host system type... i686-apple-darwin8.8.1
checking for egrep... grep -E
...
$ make
...
Build complete.
(It is safe to ignore warnings about tempnam and tmpnam).

make 命令生成 Xdebug 扩展,xdebug.so。剩下的工作就是使用 sudo make install 进行安装。

$ sudo make install
Installing shared extensions: /usr/lib/php/extensions/no-debug-non-zts-20020429/

注: 如果在终端窗口中运行最后一个命令,请选择并复制最后一步中发出的目录。在下一个步骤中将会用到它。

最后,要使配置数据可视化,必须使用 KCacheGrind 和 GraphViz。包含 K Desktop Environment (KDE)的 Linux 发行版很可能已经含有了KCacheGrind 和 GraphViz。如果没有包含,适合您所使用的 Linux 的那些版本也不难找到。Debian 用户可以使用 Advanced Packaging Tool (APT) 快速安装 KCacheGrind 和 GraphViz 以及所有包的依赖关系。

清单 3. 安装 KCacheGrind
$ apt-cache search kcachegrind
valgrind-callgrind - call-graph skin for valgrind
kcachegrind - visualisation tool for valgrind profiling output
kcachegrind-converters - format converters for KCachegrind profiling visualisation tool
$ apt-cache search graphviz
graphviz - rich set of graph drawing tools
graphviz-dev - graphviz Libs and Headers against which to build applications
graphviz-doc - additional documentation for graphviz
libdeps-renderer-dot-perl - DEPS renderer plugin using GraphViz/dot
...
$ sudo apt-get install kcachegrind graphviz 
...

如果没有将 KDE 安装到系统中,KCacheGrindGraphViz 以及所有必要的内容将占用大约 90 MB 的磁盘空间。

配置 Xdebug

安装了 Xdebug 扩展后,就可以准备启用和配置该扩展了。在文本编辑器中打开 php.ini,并添加以下代码行。

清单 4. 启用和配置该扩展
zend_extension = /usr/lib/php/extensions/no-debug-non-zts-20020429/xdebug.so
xdebug.profiler_output_dir = "/tmp/xdebug/"
xdebug.profiler_enable = Off
xdebug.profiler_enable_trigger = 1

第一行 zend_extension 加载 Xdebug 扩展。第二行命名放置分析器输出的目录。如果需要的话,创建命名的目标并更改其模式以允许用户对 Web 服务器进行写访问。

第三行禁用了分析器。然而,第四行将在设置 HTTP GET 或 POST 参数 XDEBUG_PROFILE 时启用分析器。(如果您希望一直使用分析器,在第三行代码中将 Off 更改为 On。)

添加这几行代码并验证了输出目录是可写的,然后重新启动 Web 服务器。对于其他 PHP 扩展,要验证 Xdebug 是否安装并可用,可以创建一个简单的骨架 PHP 程序来调用 phpinfo() 并查看结果。应该能够看到类似于图 1 所示的内容。(为简便起见,省略了完整输出的部分内容。)

图 1. Phpinfo 指明 Xdebug 是否已安装
Phpinfo 指明 Xdebug 是否已安装

您还可以向下滚动到 Zend 徽标。如果正确安装并配置了 Xdebug,它将显示在徽标的旁边。

使用分析器

要分析代码,只需将浏览器指向 PHP 应用程序即可。如果您将分析器设置为对每个触发逐个解决的方式,将 XDEBUG_PROFILE=1 追加到 URL 中,或者,像下面一样,将参数嵌入到表单中。

作为一个示例,我们来分析一下这个简单的 ACME Fibonacci Maker,fibonacci.php,如清单 5 所示。为方便起见,将 XDEBUG_PROFILE 参数设置在表单的隐藏变量内。(当代码投入生产时,很可能将禁用 Xdebug,呈现这个变量将不会造成什么损失。)

清单 5. Fibonacci.php
<?php
  function fib($nth = 1) {
    if ( $nth < 2 ) {
      return( $nth ); 
    }
    
    return( fib( $nth - 1) + fib( $nth - 2 ) );
  }
?> 
  
<html>
  <head>
    <title>ACME Fibonacci Maker</title>
  </head>
  <body>
    <h2>Try the ACME Fibonacci Maker!</h2>
    <form action="fibonacci.php" method="POST">
    <input type="hidden" name="XDEBUG_PROFILE" value="1" />
    Enter a number: <input type="text" name="n"></input>
    </form>
    <hr />

<?php  
  if ( ! empty( $_REQUEST['n'] ) ) {
    $n = $_REQUEST['n'] % 10;
    $suffix = array( 1 => "st", 2 => "nd", 3 => "rd" );
    if ( $_REQUEST['n'] < 4 || $_REQUEST['n'] > 20 ) {
      $suffix = $suffix[$n];
    }
    else {
      $suffix = 'th';
    }
    
    echo '<p>The ' . $_REQUEST['n'] . $suffix .' Fibonacci number is ';
    echo fib( $_REQUEST['n'] ) . '</p>';
  }
?>
  </body>
</html>

将浏览器指向 http://localhost/fibonacci.php(或者合适的 URL)并输入数字 —— 比如,16。其结果 —— Fibonacci 系列的第 16 个元素 —— 如图 2 所示。

图 2. 示例 Fibonacci 应用程序
示例 Fibonacci 应用程序

如果将分析器输出目录中的内容(名为 php.ini)列出来的话,应该能看到类似 cachegrind.out.951917687 这样名称的文件。cachegrind.out. 前缀是固定的。默认情况下,数值后缀是目录路径到 fibonacci.php 文件的 CRC32 散列。因此,如果每一个应用程序都位于自己的目录,那么每个程序的输出将根据文件名而被分隔。(如果您更喜欢将输出与时间相关联,将下面这行代码:

xdebug.profiler_output_name = timestamp

添加到 php.ini。)

从终端窗口启动 KCacheGrind 并打开 cachegrind.out.951917687。将立即打开一个类似于图 3 的新窗口。

图 3. KCacheGrind 应用程序
KCacheGrind 应用程序

单击 Callees 选项卡,双击源代码中突出显示的行,并从 Grouping 列表选择 Source File 。所看到的视图应变为类似图 4 所示的内容。

图 4. 查看结果
查看结果

正如您预期的一样,实际上全部的处理时间(70,989 毫秒的 99.87%)都花费在 3193 次对 fib() 函数的调用上了。要加快该应用程序(随着进一步执行 Fibonacci 序列,程序会随之变慢),应该避免重新计算 Fibonacci 数字这样代价高昂的重复工作。事实上,ACME Fibonacci Maker 能够很好地进行计算重用。

下面展示了 fib() 函数的优化版本。新的版本用内存换来了时间上的节省,因为它保留了中间的计算以便以后使用。图 5 展示了分析结果:与上次的 3192 次函数调用相比,这里仅需要 30 次调用(并且只有一半的调用需要计算结果),而时间则减少为只有 20 毫秒。

清单 6. 更新了的 fib() 函数
function fib($nth = 1) {
  static $fibs = array();

  if ( ! empty ($fibs[$nth] ) ) { 
    return( $fibs[$nth] );
  }
  
  if ( $nth < 2 ) {
    $fibs[$nth] = $nth;
  }
  else {  
    $fibs[$nth - 1] = fib( $nth - 1 );
    $fibs[$nth - 2] = fib( $nth - 2 );
    $fibs[$nth] = $fibs[$nth - 1] + $fibs[$nth -2];
  }
  
  return( $fibs[$nth] );
}
?>
图 5. 加快了的 Fibonacci 函数
加快了的 Fibonacci 函数

虽然单次运行应用程序能够指出一些问题(可以试试上面原始的应用程序中的 Fibonacci 序列的第 50 个元素 ),通常,还是需要通过几次调用收集统计信息以及查看模式。

如果保留默认的 “crc32” 命名模式,每次运行 fibonacci.php 时,将重写数据文件。然而,可以通过在 php.ini 中设置 xdebug.profiler_append = 1 改变这种行为并将后续运行追加到相同的文件。更改之后重新启动 Web 服务器。

图 6 显示了三次运行 Fibonacci Maker 之后数据合计的示例。总时间稍大于两秒;其中 99.97% 的时间花费在了 fib() 上。图 6 显示了 Call Graph 选项卡,它由 GraphViz 的 dot 工具生成。关于 KCacheGrind 的具体用法不在本文讨论的范围之内,但是可以从网上获得其完整的文档。KCacheGrind 可以以很多种方法对数据进行交叉分析,根据您希望解决的问题选择合适的方法。

图 6. 合计分析数据
合计分析数据

调试的更好方法

除了分析 PHP 应用程序,还可以在发生错误并进行交互式调试时,配置 Xdebug 扩展(如其名字暗示的一样)来提供详细的栈跟踪和错误消息。栈跟踪和错误消息可以指出错误的原因,而交互式调试允许每次逐步调试代码中的一条指令,查看程序变量的类型和值,并检查所有的 PHP 超全局变量,包括进来的请求参数。

本系列的下一篇文章将具体介绍交互式调试。同时,您可以启用几个 Xdebug 特性来说明应用程序在发生错误时的状态:

  • 无论何时只要应用程序出现错误,设置xdebug.default_enable=On 显示栈跟踪。如果您已经花费时间安装了 Xdebug,那么只要进行代码开发就启用这个特性。
  • 还可以设置 xdebug.show_local_vars=1 来进一步显示最顶部范围内的所有变量。
  • xdebug.var_display_max_childrenxdebug.var_display_max_data和 xdebug.var_display_max_depth> 是相关的三个设置,分别用来控制因 xdebug.show_local_vars的使用而显示的变量的属性数、字符串长度和嵌套深度。

可以在 Xdebug Web 站点找到更多信息。

分析类

如果没有具体的代码,那么很难演示具有意义的分析,下面这个示例是十分典型的代码,展示了从中所能获得的信息。清单 7 显示了一个装配玩具火箭的应用程序(人为设计)。这种玩具火箭由几个部分组成,生产每一个部分都需要一定的时间。在 PHP 中,使用类代表每个组成部分,使用实例方法表示每个部分的构造时间。您可以将这个玩具看作是一个应用程序,并把每个部分看作是该应用程序的功能。

清单 7. 模拟玩具装配的一组 PHP 类
<?php
    define( 'BOOSTER', 5 );
    define( 'CAPSULE', 2 );
    define( 'MINUTE', 60 );
    define( 'STAGE', 3 );
    define( 'PRODUCTION', 1000 );
    
    class Part {
        function Part() {
            $this->build( MINUTE );
        }
        
        function build( $delay = 0 ) {
            if ( $delay <= 0 )
                return;
                
            while ( $delay-- > 0 ) {
            }
        }
    }
    
    class Capsule extends Part {
        function Capsule() {
          parent::Part();
            $this->build( CAPSULE * MINUTE );
        }
    }
    
    class Booster extends Part {
        function Booster() {
          parent::Part();
            $this->build( BOOSTER * MINUTE );
        }
    }
    
    class Stage extends Part {
        function Stage() {
          parent::Part();
          $this->build( STAGE * MINUTE );
        }
    }
    
    class SpaceShip {
        var $booster;
        var $capsule; 
        var $stages;
        
        function SpaceShip( $numberStages = 3 ) {
            $this->booster = new Booster();
            $this->capsule = new Capsule();
            $this->stages = array();
            
            while ( $numberStages-- >= 0 ) {
                $stages[$numberStages] = new Stage();
            }
        }
    }
    
    $toys = array();
    $count = PRODUCTION;
    
    while ( $count-- >= 0  ) {
      $toys[] = new SpaceShip( 2 );
    }
?>

<html>
<head>
<title>
Toy Factory Output
</title>
</head>
<body>
  <h1>Toy Production</h1>
  <p>Built <? echo PRODUCTION . ' toys' ?></p>
</body>
</html>

运行这些代码将生成一个新的数据文件。同样,将数据加载到 KCacheGrind。如果切换到 Source 和 Call Graph 选项卡,将看到类似图 7 所示的视图。

图 7. 太空船应用程序的配置文件
太空船应用程序的配置文件

Flat Profile 窗格(左面)显示了应用程序调用的所有函数(方法)。最左面的列展示了近似的累计总数,第二列展示了每种方法的单独测试,第三列列出了调用该方法的次数。在调用图表中使用有颜色的方块反映图表内容,这非常方便,能够很容易地将事件序列与其花费的时间关联起来。

很明显,构建阶段所使用的时间代价最昂贵。构建每一部分所需的系统开销(使用 Part 的构造器表示)次之。再看一下 PHP 自身的define() 函数,它只花费了很少的开销。

最后,还可以查看内存的使用情况。从靠近顶部的下拉菜单中选择 Memory 和 Class,然后切换到顶部以及底部的 Types 和 Caller Map 选项卡。您看到的屏幕应该类似图 8。

图 8. 太空船应用程序的内存使用情况
太空船应用程序的内存使用情况

找回周期

和其他众多 PHP 扩展一样,Xdebug 容易构建、安装快捷且易于配置 —— 所有这些工作 10 分钟内即可完成。如果您已经优化了 Apache 安装并且对应用程序进行了缓存,但是性能仍然很差,那么可以考虑一下代码的运行。算法是否有效?代码是否过于复杂?是否重复实现了 PHP 已提供的函数?

当然,如果不能判断出应用程序的瓶颈所在,那么就必须进行查找并加以修复。不要只凭猜测 —— 要进行分析!您可能会惊讶于宝贵的计算周期是如何被轻意耗费掉的。

并且永远不要忘记:要在生产服务器中禁用 Xdebug,因为启用它总会增加系统开销。

JS JSP ASP .NET J2AM API接口和返回的版本 目前所有版本的JS JSP ASP .NET J2AM 都是提供源代码的,对于一些脚本语言来说,直接解压缩之后就可以使用了,不需要什么安装步骤。另外一些需要编译的语言,则提供了编译用的 shell 文件(Linux/Unix 下使用)和 bat 文件(Windows 下使用),或者直接提供编译好的二进制库文件。 不过为了让读者能够更清楚如何安装,我们还是对每种语言的安装都做详细的讲解,你可以在安装列表里找到你感兴趣的语言的安装方法。 示例 如果你已经把 JS JSP ASP .NET J2AM 安装好了,那么接下来就让我们开始第一个小程序吧。按照惯例,第一个演示程序几乎总是 HelloWorld,我们也不想打破这个惯例,不过对于 PHPRPC 来说,有服务器端就要有客户端,否则我们就没有什么好演示的啦,所以我们的第一个演示程序实际上是两个,一个是服务器端,另一个是客户端。我们都先用 PHP 语言来写好了。 服务器端 view plaincopy to clipboardprint? <?php include ("php/phprpc_server.php"); function HelloWorld() { return 'Hello World!'; } $server = new PHPRPC_Server(); $server->add('HelloWorld'); $server->start(); ?> 客户端 view plaincopy to clipboardprint? <?php include ("php/phprpc_client.php"); $client = new PHPRPC_Client('http://127.0.0.1/server.php'); echo $client->HelloWorld(); ?> 对于服务器端程序,我们应该将它命名为 server.php(这是因为客户端调用时用的是这个名字,而不是 PHPRPC 的什么规定),然后把它放在本地 Web 服务器的根目录下,并保证服务器可以正常运行 PHP 程序,之后在浏览器或命令行下运行客户端程序,你就可以看到结果了。 这两个程序几乎简单到无需解释的地步,所以如果你已经明白它们的意思,那么就可以直接跳过下面的解释,继续看后面的例子。 服务器端第 1 句是将 它的服务器端程序包含到你的程序里,之后的 2 - 4 句是定义一个远程调用的函数,你会发现它与本地函数没有任何区别。第 5 句是创建服务器端对象,第 6 句是添加要发布的方法,这里添加的就是刚刚定义的 HelloWorld 函数,在 PHP 中,添加的发布方法是函数名的字符串表示,在其它语言中可能略有不同。第 7 句是启动服务。 客户端就更简单了,第 1 句是将 它的客户端程序包含到你的程序里。第 2 句是创建客户端对象,其中的参数就是服务器端的地址。第 3 句是对远程方法(函数)的调用,之后通过 echo 将它显示出来。如果顺利的话,执行后你就会看到输出的 Hello World!。 上面的例子是发布的是函数,下面我们来看一下类中的静态方法如何发布: view plaincopy to clipboardprint? <?php include ("php/phprpc_server.php"); class Hello { static function HelloWorld() { return 'Hello World!'; } } $server = new PHPRPC_Server(); $server->add('HelloWorld', 'Hello'); $server->start(); ?> 这个服务器端只要它的名字与发布的地址与上面那个发布函数的例子一样的话,上面的那个客户端就可以得到同样的结果,也就是说,在客户端看来是没有任何区别的。 它并不是只可以在 PHP 中使用,它同样支持其它语言的服务器和客户端,而且还可以无差别的相互调用。 现在我们来看一下如何在 Java 中调用这个 PHP 的服务器方法: view plaincopy to clipboardprint? import org.phprpc.*; interface IHello { public String helloWorld(); } public class HelloWorld
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值