在两个月前一个群里的朋友问了一个问题,他说:“现在他们公司的项目有一个模块的性能在线表现非常差,很长时间没有查出问题所在,老板一怒之下让他把所有类方法的执行时间给记录进行分析,并且不能影响现在的项目性能。”老板让他记录这些信息是为了分析具体影响性能的地方在哪些地方,待项目运行一段时间就去除。这个需求导致两个个问题,第一是怎么监听这个模块所有类方法的执行时间,第二是怎么能在不影响现在项目性能的情况下完成(本身性能就很差了),下面我们就这两个问题来分析:
一、怎么监听这个模块所有类方法的执行时间
这个问题他第一个想到的是在所有类方法处理前加一段代码记录时间,在返回数据前进行计算运行时间,然后记录日志。这种方法一定是可行的,而且对性能的影响不是很大,但是…… 反正我不会这么干了,程序员都是懒惰的,而且都是很聪明的,我们不是码农,如果这么实施,那么要改动的代码量太大了,重复很久没有意义没有技术含量的工作,其次以后删除的时候又来一次,我会奔溃。当然位朋友也不会这么干,所以我才知道了这个奇葩的需求。
怎么解决呢,本来这么任务解决起来确实很费劲,我们可以借用__call()和__callStatic()对类方法进行重载,在php5.3以前静态方法只能自己一个个去加了,感谢php5.3新增了__callStatic()魔术方法。有人会问了这个两个魔术方法都是在类方法不存在的时候才有用,他们怎么能实现这个需求呢?这个问题等会看代码,下面分析第二个问题吧。
二、怎么能在不影响现在项目性能的情况下完成
为啥我说会有这个问题呢?之前分析需求的时候确实有,但是在回答第一个问题的时候已经说了解决方案,我认为采用__call()和__callStatic()对类方法进行重载,比较简单实现,且对现有项目性能影响很小。
我们在这个问题里主要讨论一些其它方法,其实实现性能分析的扩展也有很多,xdebug、xhprof等,都知道xdebug性能损耗很大,不适合在正式环境使用,xhprof性能损耗相对较小,可在正式环境使用。那为什么不用xhprof呢?有三点:1.性能损耗略大;2.日志记录格式不灵活,对日志的分析造成困扰;3.有些函数没法统计,而且会出现严重错误(如:call_user_func_array)
既然已经确定解决方案,我们就先看一个demo吧
/** * 类方法性能监听 * * @author 戚银 [email protected] * @date 2015-05-31 22:09 */ class demo { /** * 普通类方法 * * @access public * @return void */ public function test1(){ for($i=0; $i<100; ++$i){ } } /** * 静态方法 * * @access public * @return void */ public static function test2(){ for($i=0; $i<100; ++$i){ } } }
看上面的demo类,其中有两个方法,一个普通方法,一个静态方法,我们业务层调用的方式如下:
<?php (new demo())->test1(); demo::test2();
我们保证一个原则就是不改变类以外的代码,只调整该类来实现,下面我采用__call()和__callStatic()对类方法进行重载。
<?php /** * 类方法性能监听 * * @author 戚银 [email protected] * @date 2015-05-31 22:09 */ class demo { /** * 普通类方法 * * @access public * @return void */ public function _test1(){ for($i=0; $i<100; ++$i){ } } /** * 静态方法 * * @access public * @return void */ public static function _test2(){ for($i=0; $i<100; ++$i){ } } /** * 类魔术方法 * * @access public * @param string $name * @param array $arguments * @return mixed */ public function __call($name, $arguments){ $startTime = microtime(true); $data = call_user_func_array(array($this, '_'.$name), $arguments); echo microtime(true) - $startTime; return $data; } /** * 类魔术方法 * * @access public * @param string $name * @param array $arguments * @return mixed */ public static function __callStatic($name, $arguments){ $startTime = microtime(true); $data = call_user_func_array(array(__CLASS__, '_'.$name), $arguments); echo microtime(true) - $startTime; return $data; } }
在这段代码里面我们添加了__call()和__callStatic()方法,如果只添加这两个方法是没用的,因为之前业务层的代码没变,调用的方法是存在的,要使调用的方法不存在而且只改变类本身,就只能在方法名前加一个下划线(这个规则自己定),然后我们在调用这个两个方法发现输出了两个方法的执行时间。
这样实现也发现了几个问题,改动的代码还是很多,每个类都要加,很累…………,其实善用手边的工具很好实现,使用继承是个很好的方式,把这个两个方法写到一个基类里,然后所有类都这继承这个基类。类方法名替换,除了构造方法和析构方法,直接使用编辑器批量替换即可,以后改回来也是。
注意:如果使用继承方式实现,__callStatic()方法的__CLASS__需要调整。
这里以在类方法前增加下划线实现,使业务层找不到类方法,其实这个例子还可以调整方法可见性来实现,但是可见性实现的方式有以下弊端:
1. 调整后以后想调整回来很有可能弄错类方法可见性,不知道哪些方法调整了。
2. 调整可见性只对类公共方法有效,对受保护和私用的方法不可用
当然如果只记录类公共方法的性能,可以使用改变方法可见性实现,但一定记得在注视上添加标注该方法是改变过的,而且一定被把public改成private,因为如果有类继承这个类就没法访问这个方法了。