PHP前端开发中的性能那点事

澳门新葡亰网址 4

1. 关于代码覆盖率

衡量代码覆盖率有很多种层次,比如行覆盖率,函数/方法覆盖率,类覆盖率,分支覆盖率等等。代码覆盖率也是衡量测试质量的一个重要标准,对于黑盒测试来说,如果你不确定自己的测试用例是否真正跑过了系统里面的每一行代码,在测试的完整性上总要打些折扣。因此,业界几乎对各种编程语言都有自己的一套代码覆盖率解决方案。世界上最美的语言PHP当然也不例外。PHPUnit和Spike
PHPCoverage提供了一套基于xdebug的代码覆盖率测试方案。在本文中,我将针对自己碰到的特定业务场景,讲述一下自己进行PHP代码函数覆盖率测试的解决方案。

PHP前端开发中的性能那点事

 在我们平时的php开发中,一个大的项目经过长时间的积累以后你会发现性能越来越慢,而性能到底消耗在了什么地方,常常是一个令人头疼的问题,function
a()调用了多少次,function
b()又消耗了多少时间,我们到底怎么查找是哪个蛀虫拉慢了我们的程序运行速度呢?在这里给大家介绍一款工具xdebug,相信很多人已经听说过了,希望借助这个工具我们可以起到简单分析php程序性能瓶颈的问题。

A)假设1,假设用户目录在/home/ad
B)假设2,假设php目录在/home/ad/php

1、xdebug简介与安装
Xdebug是一个开放源代码的PHP程序调试器(即一个Debug工具),可以用来跟踪,调试和分析PHP程序的运行状况。
1)下载xdebug
xdebug的官方下载地址为:
最新版本为:Xdebug 2.1.0
2)xdebug的安装

1

2

3

4

5

6

7

8

cd /home/ad

wget  http://xdebug.org/files/xdebug-2.1.0.tgz

tar -zxvf xdebug-2.1.0.tgz

cd xdebug-2.1.0

/home/ad/php/bin/phpize

./configure –enable-xdebug –with-php-config=/home/ad/php/bin/php-config

make

make install

安装完以后会提示你扩展安装到了哪个目录,类似 
/home/ad/php/lib/php/extensions/no-debug-non-zts-20060613/
假设你的php.ini放在 /home/ad/php/lib/php.ini
加上

1

2

3

4

5

6

7

8

9

[xdebug]

zend_extension = "/home/ad/php/lib/php/extensions/no-debug-non-zts-20060613/xdebug.so"

xdebug.auto_trace = on

xdebug.auto_profile = on

xdebug.collect_params = on

xdebug.collect_return = on

xdebug.profiler_enable = on

xdebug.trace_output_dir = "/home/ad/xdebug_log"

xdebug.profiler_output_dir = "/home/ad/xdebug_log"

重启apache
去/home/ad/xdebug_log下看看是不是日志已经出来了

2、xdebug参数简介
zend_extension 加载xdebug扩展
xdebug.auto_trace 自动打开打开函数调用监测
xdebug.auto_profile 自动打开性能监测
xdebug.trace_output_dir 设定函数调用监测信息的输出文件的路径。
xdebug.profiler_output_dir 设定效能监测信息输出文件的路径。
xdebug.collect_params
打开收集“函数参数”的功能。将函数调用的参数值列入函数过程调用的监测信息中。
xdebug.collect_return
打开收集“函数返回值”的功能。将函数的返回值列入函数过程调用的监测信息中。

3、示例程序与日志收集

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<?php

function a()

{

    echo "aaa";   

}

function b()

{

    a();

    sleep(1);

    a();

    sleep(1);

    a();   

}

b();

?>

 

 

4、日志分析工具wincachegrind

不用安装直接双击就可以打开了
我们用它打开刚才收集的日志cachegrind.out.***

 

前端开发中的性能那点事(二)巧用curl 并发减少后端访问时间

前言:
在我们平时的程序中难免出现同时访问几个接口的情况,平时我们用curl进行访问的时候,一般都是单个、顺序访问,假如有3个接口,每个接口耗时500毫秒那么我们三个接口就要花费1500毫秒了,这个问题太头疼了严重影响了页面访问速度,有没有可能并发访问来提高速度呢?今天就简单的说一下,利用curl并发来提高页面访问速度,
希望大家多指导。
1、老的curl访问方式以及耗时统计

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

<?php

function curl_fetch($url, $timeout=3){

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $url);

    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    $data = curl_exec($ch);

    $errno = curl_errno($ch);

    if ($errno>0) {

        $data = false;

    }

    curl_close($ch);

    return $data;

}

function microtime_float()

{

   list($usec, $sec) = explode(" ", microtime());

   return ((float)$usec + (float)$sec);

}

$url_arr=array(

     "taobao"=>"http://www.taobao.com",

     "sohu"=>"http://www.sohu.com",

     "sina"=>"http://www.sina.com.cn",

     );

 $time_start = microtime_float();

 $data=array();

 foreach ($url_arr as $key=>$val)

 {

     $data[$key]=curl_fetch($val);

 }

 $time_end = microtime_float();

 $time = $time_end – $time_start;

 echo "耗时:{$time}";

?>

耗时:0.614秒
2、curl并发访问方式以及耗时统计

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

<?php

function curl_multi_fetch($urlarr=array()){

    $result=$res=$ch=array();

    $nch = 0;

    $mh = curl_multi_init();

    foreach ($urlarr as $nk => $url) {

        $timeout=2;

        $ch[$nch] = curl_init();

        curl_setopt_array($ch[$nch], array(

        CURLOPT_URL => $url,

        CURLOPT_HEADER => false,

        CURLOPT_RETURNTRANSFER => true,

        CURLOPT_TIMEOUT => $timeout,

        ));

        curl_multi_add_handle($mh, $ch[$nch]);

        ++$nch;

    }

    /* wait for performing request */

    do {

        $mrc = curl_multi_exec($mh, $running);

    } while (CURLM_CALL_MULTI_PERFORM == $mrc);

  

    while ($running && $mrc == CURLM_OK) {

        // wait for network

        if (curl_multi_select($mh, 0.5) > -1) {

            // pull in new data;

            do {

                $mrc = curl_multi_exec($mh, $running);

            } while (CURLM_CALL_MULTI_PERFORM == $mrc);

        }

    }

  

    if ($mrc != CURLM_OK) {

        error_log("CURL Data Error");

    }

  

    /* get data */

    $nch = 0;

    foreach ($urlarr as $moudle=>$node) {

        if (($err = curl_error($ch[$nch])) == ”) {

            $res[$nch]=curl_multi_getcontent($ch[$nch]);

            $result[$moudle]=$res[$nch];

        }

        else

        {

            error_log("curl error");

        }

        curl_multi_remove_handle($mh,$ch[$nch]);

        curl_close($ch[$nch]);

        ++$nch;

    }

    curl_multi_close($mh);

    return  $result;

}

$url_arr=array(

     "taobao"=>"http://www.taobao.com",

     "sohu"=>"http://www.sohu.com",

     "sina"=>"http://www.sina.com.cn",

     );

function microtime_float()

{

   list($usec, $sec) = explode(" ", microtime());

   return ((float)$usec + (float)$sec);

}

$time_start = microtime_float();

$data=curl_multi_fetch($url_arr);

$time_end = microtime_float();

$time = $time_end – $time_start;

 echo "耗时:{$time}";

?>

 

 

耗时:0.316秒
帅气吧整个页面访问后端接口的时间节省了一半
3、curl相关参数
来自:
curl_close — Close a cURL session
curl_copy_handle — Copy a cURL handle along with all of its
preferences
curl_errno — Return the last error number
curl_error — Return a string containing the last error for the current
session
curl_澳门新葡亰网址,exec — Perform a cURL session
curl_getinfo — Get information regarding a specific transfer
curl_init — Initialize a cURL session
curl_multi_add_handle — Add a normal cURL handle to a cURL multi
handle
curl_multi_close — Close a set of cURL handles
curl_multi_exec — Run the sub-connections of the current cURL handle
curl_multi_getcontent — Return the content of a cURL handle if
CURLOPT_RETURNTRANSFER is set
curl_multi_info_read — Get information about the current transfers
curl_multi_init — Returns a new cURL multi handle
curl_multi_remove_handle — Remove a multi handle from a set of cURL
handles
curl_multi_select — Wait for activity on any curl_multi connection
curl_setopt_array — Set multiple options for a cURL transfer
curl_setopt — Set an option for a cURL transfer
curl_version — Gets cURL version information

前端开发中的性能那点事(三)php的opcode缓存

 

前言:由php的运行机制决定,其实php在运行阶段我们也是可以进行缓存的从而提高程序运行效率,这就是我们常说的opcode缓存。
1、简述php的运行机制
(因为本文是写opcode缓存的所以这里只是简要概述,后边会专门写一篇揭秘php运行机制的。)
a).php文件通过浏览器过来
b)请求交给SAPI,随后SAPI层将控制权转给PHP
c)zend_language_scanner对代码进行扫描,对php代码进行词法分析转换成一系列的tokens
array
d)zend_language_parser将c步骤产生的一系列tokens处理掉空格等无用的代码以后转换成一系列表达式
e)经过compiler阶段生成opcode返回zend_op_array指针
f)zend_vm_execute根据传入的zend_op_array指针,执行opcode并将结果返回输出

澳门新葡亰网址 1

2、opcode简介
Opcode是operation
code(操作码)的简称,其实就是第一小节c)、d)、e)步骤产生的一种中间码,
opcode是一个四元组,(opcode, op1, op2,
result),它们分别代表操作码,第一操作数,第二操作数,结果。
如:

1

2

3

<?php

echo "taobao search blog";

?>

对应的tokens

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

Array

(

    [0] => Array

        (

            [0] => 367

            [1] => <?php

            [2] => 1

        )

  

    [1] => Array

        (

            [0] => 316

            [1] => echo

            [2] => 1

        )

  

    [2] => Array

        (

            [0] => 370

            [1] =>

            [2] => 1

        )

  

    [3] => Array

        (

            [0] => 315

            [1] => "taobao search blog"

            [2] => 1

        )

  

    [4] => ;

    [5] => Array

        (

            [0] => 370

            [1] =>

            [2] => 1

        )

  

    [6] => Array

        (

            [0] => 369

            [1] => ?>

            [2] => 1

        )

  

)

对应的opcode就是

1

2

3

4

5

line     # *  op                           fetch          ext  return  operands

———————————————————————————

   2     0  >   ECHO                                                     ‘taobao+search+blog’

   4     1    > RETURN                                                   1

         2*   > ZEND_HANDLE_EXCEPTION

3、使用apc对opcode缓存
a)假设php路径为/home/ad/php
对opcode进行缓存的软件很多(apc、eAcclerator、Xcache、Zend
Platform),这里主要介绍apc
APC提供两种缓存功能,即缓存Opcode(目标文件),我们称之为apc_compiler_cache。同时它还提供一些接口用于PHP开发人员将用户数据驻留在内存中,我们称之为apc_user_cache。我们这里主要讨论apc_compiler_cache的配置。
下载地址:
最新版本为APC-3.1.6.tgz

1

2

3

4

5

6

7

wget http://pecl.php.net/get/APC-3.1.6.tgz

tar -zxvf APC-3.1.6.tgz

cd APC-3.1.6

/home/ad/php/bin/phpize

./configure –enable-apc –enable-apc-mmap  –with-php-config=/home/ad/php/bin/php-config

make

make install

编辑php.ini
添加apc的配置

1

2

3

4

5

6

7

8

9

10

11

12

13

[apc]

extension=apc.so

apc.enabled=1

apc.shm_segments = 1

apc.shm_size = 128

apc.ttl = 0

apc.user_ttl = 7200

apc.num_files_hint = 1000

apc.write_lock=1

apc.stat = 0

apc.max_file_size=1M

apc.filters = a.php,b.php

apc.cache_by_default=1

重新apache就ok啦

4、常用参数的解析
apc.enabled 开启apc 设置为0关闭,1为开启
apc.shm_segments 共享内存块数
apc.shm_size 共享内存大小,但是是M
那么显然共享内存的总数就是apc.shm_segments*apc.shm_size
apc.num_files_hint 允许多少个opcode被缓存
apc.stat
为1的时候会自动检查opcode对应的php文件是否有更新,有更新的话会自动更新。设置为0的话就不会去检查了这样会提高apc的效率,但是要使php的修改生效的话就必须重启apache了,或者使用函数apc_cache_clear()来清空缓存
apc.ttl
opcode缓存的过期时间,设置为0表示不过期,如果不为0会检查两次请求之间的时间,如果时间大于设置值那么会更新opcode缓存
apc.write_lock
表示多个进程同时更新一份opcode缓存的时候那么只让最先的一个生效,可以有效避免写冲突
apc.max_file_size 超过设置值大小的文件不被缓存
apc.filters 需要特例的文件,多个文件用逗号(,)相隔
apc.filters 与 apc.cache_by_default结合使用,
当apc.cache_by_default为1时apc.filters文件不被缓存,当apc.cache_by_default为0时仅apc.filters文件被缓存

在我们平时的php开发中,一个大的项目经过长时间的积累以后你会发现性能越来越慢,而性能到底消耗在了什么…

3. 函数覆盖率解决方案

(1)原理

xdebug天生提供了对行覆盖率的支持,我们要自己计算出函数覆盖率。函数覆盖率需要两点数据,一个是哪些函数被执行,一个是文件中总共有多少个函数。

文件中总共的函数量,由于我们不可能把所有函数都执行一遍,因此这部分只能通过代码静态扫描来实现。如果是在C++或者Java中,可能就需要词法分析工具了,然而在最美的语言PHP面前,我们完全不需要那么复杂。从PHP4.3开始,PHP
Zend
Engine中内置了tokenizer功能,帮助开发者做源码词法分析。我们只需要找到PHP中定义函数时所对应的词法规律,就可以轻松得到指定PHP文件中的全部函数了。

tokenizer定义的接口也十分简单:

array token_get_all (string $source)

该函数进行文件解析,将php源代码拆成由token组成的数组。

string token_name (int $token)

将整数形式的token转变为字符串形式。类似于C语言中的strerror函数。有了tokenizer,自己再根据php函数定义的规律和格式设计一个有限状态机,即可完成全量函数的解析。这部分代码,本人写了个比较简陋的,把它单独拿出来,仅供大家参考:PHPFunctionParser

求函数覆盖率的另外一个难点在于获取被执行的函数列表。这地方让我们走了一些弯路。一开始一个最简单的办法,我们既然通过xdebug拿到被执的行,可以通过行号来反推此行属于哪一个函数。然而每一次的请求获取的行号信息量是非常大的,如果一个求情执行了1000行,那就要进行1000次判断,效率上会比较差。调研了一番之后,发现xdebug提供了function
trace的功能,可以把一次请求中的函数调用关系获取到,只不过拿到了函数名字,却没办法得到它所在的文件。于是,再次调研一番,发现了Reflection,给定方法名和类名,可以反推出来它在哪个文件中定义。于是我们使用function
trace把函数调用关系暂存在一个临时文件中,然后通过文件解析,拿到执行的函数名(如果是类方法,则是“类名::函数名”的形式),再通过reflection机制反推出定义这个函数的文件即可。再次体会到了世界上最美语言的强大之处。

(2)插桩

为了降低使用门槛,我们尽可能少地改变PHP源代码为好。xdebug收集信息的原理是分别调用xdebug_start_code_coverage和xdebug_stop_code_coverage来控制覆盖率信息收集的开始和结束,因此不可避免地要改变源代码。此处我们的解决办法是,将xdebug_stop_code_coverage通过register_shutdown_function注册为php程序结束前必须要跑的一段程序(类似C语言的atexit函数),将其封装到一个文件中,然后在源代码第一行require这个文件即可。如果你的PHP框架是CodeIgniter这种所有请求都有一个统一入口index.php的框架,那就只需要改变这一个文件即可,对源代码只有一行的改动!实际上,目前基本上所有的PHP框架,都是以一个index.php文件作为所有请求的入口。

我们对源代码的改动只有入口文件index.php的第一行加入了一句话:

require_once "/file/path/to/phpcoverage.php"; ?>

而phpcoverage.php核心代码逻辑大致如下:

<?php
 ……
function xdebugPhpcoverageBeforeShutdown(){
 ……
 $lineCovData = xdebug_get_code_coverage();
 xdebug_stop_code_coverage();
 ……
 xdebug_stop_trace();
 ……
}
register_shutdown_function(‘xdebugPhpcoverageBeforeShutdown’);
……
xdebug_start_trace(……);
xdebug_start_code_coverage();
//备注:上面省略号表示非关键代码,这里就不展示了

(3)信息存储

我们的函数覆盖率测试有了思路,使用xdebug的function
trace获取一次请求中所有函数的调用关系,得到执行过的所有函数,输出到文件中,通过文件解析和reflection获得被执行的函数名和该函数所在文件。将这些信息存入数据库或文件即可。

之前试用Spike的时候,我们发现这些信息以xml格式存入文件,数据冗余度很高,导致几个测试下来,文件已经非常大了。这显然不是我们想看到的。因此在数据存储的时候,我们直接将数据做json格式的序列化,字符串形式存在文件中,大大减少了文件大小。与此同时,我们再通过请求来源的IP和日期作为分隔,分别存储不同的文件。这样,来自每个机器每天的请求数据都能一目了然,向着“精准”的方向又迈进了一步,可以对测试人员的每个请求做精确的监控。下图是我们在业务实践中搜集的部分数据文件截图:

澳门新葡亰网址 2

这样,来自任何一个IP的每一次Web请求,它所覆盖的行和函数信息,都会被记录到文件中。对于一般的项目测试中,也就只有几个测试人员在使用,所以不需要考虑一些性能问题。

帮客评论

4. 报告生成

上面讲了生成覆盖率数据的原理,不过我们至此获得的只是一份份的数据文件,如何汇总成一份完整的报告呢?这就需要我们自己来写一段脚本解析刚才生成的数据文件了。我们的做法是借鉴了开源工具spike
phpcoverage的模版,并加入自己的代码逻辑,特别是加入了该工具所不具有的函数覆盖率统计数据。我们自己测试的web页面生成的报告如下:

澳门新葡亰网址 3

图中可以看到每个文件的行覆盖率,函数覆盖率,还有总的覆盖率统计数据。如果需要更精确的数据,可以点进文件连接,查看到底覆盖的是哪些代码行(蓝色为覆盖,红色为未覆盖):

澳门新葡亰网址 4

相关文章

相关搜索:

今天看啥

搜索技术库

返回首页

  • Xdebug配置
  • phpstudy
    2013经常启动失败怎么办?
  • PHP文件上传类型后辍名对应mine对照表
  • 利用php函数获取爱站和chinaz百度权重的方法
  • 利用php函数自动获取alexa世界排名数据方法
  • php生成HTML文件的应用和原理笔记

相关频道:
PHP教程  WEB编程教程  Jsp教程  Python教程  Asp.Net教程  Ruby教程  ASP教程  PHP函数  

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图