PHP 성능 분석 및 실험: 미세한 성능 분석
이전 기사에서는 PHP의 성능을 세 가지 측면에서 논의했습니다. PHP는 해석된 언어, 동적 언어, 기본 구현입니다. 이 기사에서는 PHP의 미시적 수준에 대해 자세히 살펴보고 코드를 사용하고 작성하는 동안 주의와 개선이 필요할 수 있는 PHP의 성능 측면을 이해해 보겠습니다.
분석을 시작하기 전에 성능 분석과 관련된 몇 가지 기능을 숙지해야 합니다. 이러한 기능을 통해 프로그램 성능을 더 잘 분석하고 평가할 수 있습니다.
1. 성능 분석 관련 함수 및 명령어
1.1 시간 측정 기능
보통 time() 함수를 사용하는데, 그러나 반환되는 것은 초 수입니다. 특정 코드 조각의 내부 성능 분석의 경우 초 단위의 정확도로는 충분하지 않습니다. 따라서 마이크로타임 기능을 사용하세요. 마이크로타임 함수는 두 가지 형식을 반환할 수 있습니다. 하나는 문자열 형식이고 다른 하나는 부동 소수점 숫자 형식입니다. 그러나 기본적으로 반환되는 정밀도는 소수점 이하 4자리에 불과하다는 점에 유의해야 합니다. 더 높은 정확도를 얻으려면 정밀도를 구성해야 합니다.
마이크로타임을 사용한 결과는 다음과 같습니다.
$start= microtime(true); echo $start."/n"; $end = microtime(true); echo $end."/n"; echo ($end-$start)."/n";
출력은 다음과 같습니다.
bash-3.2# phptime.php 1441360050.3286 1441360050.3292 0.00053000450134277
코드 앞에 한 줄을 추가합니다.
ini_set("precision", 16);
출력은 다음과 같습니다.
bash-3.2# phptime.php 1441360210.932628 1441360210.932831 0.0002031326293945312
마이크로타임 내부 통계 외에도 getrusage를 사용하여 사용자 모드 기간을 얻을 수도 있습니다. 실제 작업에서 시간 명령은 전체 프로그램의 실행 시간을 계산하는 데에도 일반적으로 사용됩니다. 이를 여러 번 실행하거나 코드를 수정하면 서로 다른 시간 길이를 얻어 효율성의 차이를 얻을 수 있습니다. 구체적인 사용법은 다음과 같습니다. time phptime.php 프로그램이 완료된 후 정상적으로 종료되고 종료되는지 여부에 관계없이 관련 통계가 있습니다.
bash-3.2# time phptime.php 1441360373.150756 1441360373.150959 0.0002031326293945312 real 0m0.186s user 0m0.072s sys 0m0.077s
이 기사에서 논의된 성능 문제로 인해 우리는 코드에 시간 통계 코드가 존재하지 않도록 하기 위해 종종 수백만 번의 호출 후 격차와 추세를 분석합니다. 시간 명령은 나중에.
1.2. 메모리 사용량 관련 함수
메모리 사용량을 분석하는 함수는 memory_get_usage와 memory_get_peak_usage 두 가지가 있습니다. 즉, 현재 사용량 메모리는 피크 기간 동안 지금까지 사용된 메모리를 가져옵니다. 사용된 메모리는 바이트 단위입니다.
$base_memory= memory_get_usage(); echo "Hello,world!/n"; $end_memory= memory_get_usage(); $peak_memory= memory_get_peak_usage(); echo $base_memory,"/t",$end_memory,"/t",($end_memory-$base_memory),"/t", $peak_memory,"/n";
출력되는 내용은 다음과 같습니다.
bash-3.2# phphelloworld.php Hello,world! 224400 224568 168 227424
보시다시피 프로그램 중간에 한 문장만 출력되더라도 변수 저장 공간을 더하면 168바이트가 됩니다. 메모리가 소모됩니다.
동일한 프로그램에 대해 서로 다른 PHP 버전의 메모리 사용량은 동일하지 않거나 매우 다릅니다.
$baseMemory= memory_get_usage(); class User { private $uid; function __construct($uid) { $this->uid= $uid; } } for($i=0;$i<100000;$i++) { $obj= new User($i); if ( $i% 10000 === 0 ) { echo sprintf( '%6d: ', $i), memory_get_usage(), " bytes/n"; } } echo " peak: ",memory_get_peak_usage(true), " bytes/n";
PHP 5.2에서 메모리 사용량은 다음과 같습니다.
[root@localhostphpperf]# php52 memory.php 0: 93784 bytes 10000: 93784 bytes …… 80000: 93784 bytes 90000: 93784 bytes peak: 262144 bytes
PHP 5.3에서 메모리 사용량은 다음과 같습니다
[root@localhostphpperf]# phpmemory.php 0: 634992 bytes 10000: 634992 bytes …… 80000: 634992 bytes 90000: 634992 bytes peak: 786432 bytes
PHP 5.3의 메모리 사용량은 다음과 같습니다. 좀 더 거칠습니다.
PHP 5.4 – 5.6은 일부 최적화를 제외하고 거의 동일합니다.
[root@localhostphpperf]# php56 memory.php 0: 224944 bytes 10000: 224920 bytes …… 80000: 224920 bytes 90000: 224920 bytes peak: 262144 bytes
PHP 7을 소량 사용하면 최대 메모리 사용량이 많이 늘어납니다.
[root@localhostphpperf]# php7 memory.php 0: 353912 bytes 10000: 353912 bytes …… 80000: 353912 bytes 90000: 353912 bytes peak: 2097152 bytes
위에서 볼 수 있듯이 위에서 사용된 PHP는 100,000번의 초기화 이후에는 객체 초기화가 증가해도 메모리 사용량이 증가하지 않는 비교적 좋은 가비지 수집 메커니즘을 가지고 있습니다. PHP7은 최대 메모리 사용량이 거의 2M에 달합니다.
또 다른 예를 살펴보겠습니다. 위 코드를 기반으로 다음과 같이 한 줄을 추가합니다.
$obj->self = $obj;
코드는 다음과 같습니다.
$baseMemory= memory_get_usage(); class User { private $uid; function __construct($uid) { $this->uid= $uid; } } for($i=0;$i<100000;$i++) { $obj= new User($i); $obj->self = $obj; if ( $i% 5000 === 0 ) { echo sprintf( '%6d: ', $i), memory_get_usage(), " bytes/n"; } } echo " peak: ",memory_get_peak_usage(true), " bytes/n";
At 이번에는 메모리 사용량을 살펴보겠습니다. 가운데 표의 주요 부분은 메모리 사용량(바이트)입니다.
차트는 다음과 같습니다.
PHP 5.2에는 적절한 가비지 수집 메커니즘이 없으므로 더 많은 오류가 발생합니다. 그리고 더 많은 메모리 사용량. 5.3 이후에는 메모리 재활용 메커니즘으로 인해 메모리가 일정 범위에서 안정적으로 유지됩니다. 그리고 PHP7이 가장 적은 메모리를 사용한다는 것도 알 수 있습니다. PHP 5.2 그래픽을 제거한 후에는 대비가 더욱 분명해졌습니다.
PHP7은 알고리즘 효율성을 크게 향상시켰을 뿐만 아니라 대규모 메모리 사용을 크게 최적화했음을 알 수 있습니다(작은 프로그램의 최대 메모리 비율에도 불구하고). 기록 버전은 더 많은 메모리를 사용합니다).
1.3. 가비지 수집 관련 기능
PHP에서는 가비지 수집을 명시적으로 끄거나 켤 수 있습니다. zend_gc=. 끄면 가비지 수집을 끌 수 있습니다. 기본적으로 켜져 있습니다. 또 다른 방법은 각각 gc _enable() 및 gc _disable() 함수를 통해 가비지 수집을 켜고 끄는 것입니다.
예를 들어 위의 예를 바탕으로 가비지 컬렉션을 끄면 다음과 같은 데이터 테이블과 차트를 얻을 수 있습니다.
코드는 다음과 같습니다.
gc_disable(); $baseMemory= memory_get_usage(); class User { private $uid; function __construct($uid) { $this->uid= $uid; } } for($i=0;$i<100000;$i++) { $obj= new User($i); $obj->self = $obj; if ( $i% 5000 === 0 ) { echo sprintf( '%6d: ', $i), memory_get_usage(), " bytes/n"; } } echo " peak: ",memory_get_peak_usage(true), " bytes/n";
PHP 5.3, PHP5.4, PHP5.5, PHP5.6, PHP7에서 각각 실행하여 다음과 같은 메모리 사용량 통계표를 얻습니다.
图表如下,PHP7 还是内存使用效率最优的。
从上面的例子也可以看出来,尽管在第一个例子中,PHP7 的高峰内存使用数是最多的,但是当内存使用得多时,PHP7 的内存优化就体现出来了。
这里值得一提的是垃圾回收,尽管会使内存减少,但是会导致速度降低,因为垃圾回收也是需要消耗 CPU 等其他系统资源的。Composer 项目就曾经因为在计算依赖前关闭垃圾回收,带来成倍性能提升,引发广大网友关注。详见:
https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799
在常见的代码和性能分析中,出了以上三类函数之外,还常使用的有堆栈跟踪函数、输出函数,这里不再赘述。
二、PHP 性能分析10则
下面我们根据小程序来验证一些常见的性能差别。
2.1、使用 echo 还是 print
在有的建议规则中,会建议使用 echo ,而不使用 print。说 print 是函数,而 echo 是语法结构。实际上并不是如此,print 也是语法结构,类似的语法结构,还有多个,比如 list、isset、require 等。不过对于 PHP 7 以下 PHP 版本而言,两者确实有性能上的差别。如下两份代码:
for($i=0; $i<1000000; $i++) { echo("Hello,World!"); } for($i=0; $i<1000000; $i++) { print ("Hello,World!"); }
在 PHP 5.3 中运行速度分别如下(各2次):
[root@localhostphpperf]# time php echo1.php > /dev/null real 0m0.233s user 0m0.153s sys 0m0.080s [root@localhostphpperf]# time php echo1.php > /dev/null real 0m0.234s user 0m0.159s sys 0m0.073s [root@localhostphpperf]# time phpecho.php> /dev/null real 0m0.203s user 0m0.130s sys 0m0.072s [root@localhostphpperf]# time phpecho.php> /dev/null real 0m0.203s user 0m0.128s sys 0m0.075s
在 PHP5.3 版中效率差距10%以上。而在 PHP5.4 以上的版本中,区别不大,如下是 PHP7 中的运行效率。
[root@localhostphpperf]# time php7 echo.php> /dev/null real 0m0.151s user 0m0.088s sys 0m0.062s [root@localhostphpperf]# time php7 echo.php> /dev/null real 0m0.145s user 0m0.084s sys 0m0.061s [root@localhostphpperf]# time php7 echo1.php > /dev/null real 0m0.140s user 0m0.075s sys 0m0.064s [root@localhostphpperf]# time php7 echo1.php > /dev/null real 0m0.146s user 0m0.077s sys 0m0.069s
正如浏览器前端的一些优化准则一样,没有啥特别通用的原则,往往根据不同的情况和版本,规则也会存在不同。
2.2、require 还是 require_once?
在一些常规的优化规则中,会提到,建议使用 require_ once 而不是 require,现由是 require_ once 会去检测是否重复,而 require 则不需要重复检测。
在大量不同文件的包含中,require_ once 略慢于 require。但是 require_ once 的检测是一项内存中的行为,也就是说即使有数个需要加载的文件,检测也只是内存中的比较。而 require 的每次重新加载,都会从文件系统中去读取分析。因而 require_ once 会比 require 更佳。咱们也使用一个例子来看一下。
str.php global$str; $str= "China has a large population"; require.php for($i=0; $i<100000; $i++) { require "str.php"; } require_once.php for($i=0; $i<100000; $i++) { require_once"str.php"; }
上面的例子,在 PHP7 中,require_ once.php 的运行速度是 require.php 的30倍!在其他版本也能得到大致相同的结果。
[root@localhostphpperf]# time php7 require.php real 0m1.712s user 0m1.126s sys 0m0.569s [root@localhostphpperf]# time php7 require.php real 0m1.640s user 0m1.113s sys 0m0.515s [root@localhostphpperf]# time php7 require_once.php real 0m0.066s user 0m0.063s sys 0m0.003s [root@localhostphpperf]# time php7 require_once.php real 0m0.057s user 0m0.052s sys 0m0.004s
从上可以看到,如果存在大量的重复加载的话,require_ once 明显优于 require,因为重复的文件不再有 IO 操作。即使不是大量重复的加载,也建议使用 require_ once,因为在一个程序中,一般不会存在数以千百计的文件包含,100次内存比较的速度差距,一个文件包含就相当了。
2.3、单引号还是双引号?
单引号,还是双引号,是一个问题。一般的建议是能使用单引号的地方,就不要使用双引号,因为字符串中的单引号,不会引起解析,从而效率更高。那来看一下实际的差别。
classUser { private $uid; private $username; private $age; function __construct($uid, $username,$age){ $this->uid= $uid; $this->username = $username; $this->age = $age; } function getUserInfo() { return "UID:".$this->uid." UserName:".$this->username." Age:".$this->age; } function getUserInfoSingle() { return 'UID:'.$this->uid.' UserName:'.$this->username.' Age'.$this->age; } function getUserInfoOnce() { return "UID:{$this->uid}UserName:{$this->username} Age:{$this->age}"; } function getUserInfoSingle2() { return 'UID:{$this->uid} UserName:{$this->username} Age:{$this->age}'; } } for($i=0; $i<1000000;$i++) { $user = new User($i, "name".$i, $i%100); $user->getUserInfoSingle(); }
在上面的 User 类中,有四个不同的方法,完成一样的功能,就是拼接信息返回,看看这四个不同的方法的区别。
第一个、getUserInfo
,使用双引号和属性相拼接
[root@localhostphpperf]# time php7 string.php real 0m0.670s user 0m0.665s sys 0m0.002s [root@localhostphpperf]# time php7 string.php real 0m0.692s user 0m0.689s sys 0m0.002s [root@localhostphpperf]# time php7 string.php real 0m0.683s user 0m0.672s sys 0m0.004s
第二个、getUserInfoSingle
,使用单引号和属性相拼接
[root@localhostphpperf]# time php7 string.php real 0m0.686s user 0m0.683s sys 0m0.001s [root@localhostphpperf]# time php7 string.php real 0m0.671s user 0m0.666s sys 0m0.003s [root@localhostphpperf]# time php7 string.php real 0m0.669s user 0m0.666s sys 0m0.002s
可见在拼接中,单双引号并无明显差别。
第三个、getUserInfoOnce
,不再使用句号.
连接,而是直接引入在字符串中解析。
[root@localhostphpperf]# time php7 string.php real 0m0.564s user 0m0.556s sys 0m0.006s [root@localhostphpperf]# time php7 string.php real 0m0.592s user 0m0.587s sys 0m0.004s [root@localhostphpperf]# time php7 string.php real 0m0.563s user 0m0.559s sys 0m0.003s
从上面可见,速度提高了0.06s-0.10s,有10%-20%的效率提升。可见连缀效率更低一些。
第四个、getUserInfoSingle2
虽然没有达到我们真正想要的效果,功能是不正确的,但是在字符串中,不再需要解析变量和获取变量值,所以效率确实有大幅度提升。
[root@localhostphpperf]# time php7 string.php real 0m0.379s user 0m0.375s sys 0m0.003s [root@localhostphpperf]# time php7 string.php real 0m0.399s user 0m0.394s sys 0m0.003s [root@localhostphpperf]# time php7 string.php real 0m0.377s user 0m0.371s sys 0m0.004s
效率确实有了大的提升,快了50%。
那么这个快,是由于不需要变量引用解析带来的,还是只要加入$
天然的呢?我们再试着写了一个方法。
functiongetUserInfoSingle3() { return "UID:{\$this->uid} UserName:{\$this->username} Age:{\$this->age}"; }
得到如下运行时间:
[root@localhostphpperf]# time php7 string.php real 0m0.385s user 0m0.381s sys 0m0.002s [root@localhostphpperf]# time php7 string.php real 0m0.382s user 0m0.380s sys 0m0.002s [root@localhostphpperf]# time php7 string.php real 0m0.386s user 0m0.380s sys 0m0.004s
发现转义后的字符串,效率跟单引号是一致的,从这里也可以看见,单引号还是双引号包含,如果不存在需要解析的变量,几乎没有差别。如果有需要解析的变量,你也不能光用单引号,要么使用单引号和连缀,要么使用内部插值,所以在这条规则上,不用太过纠结。
2.4、错误应该打开还是关闭?
在 PHP 中,有多种错误消息,错误消息的开启是否会带来性能上的影响呢?从直觉觉得,由于错误消息,本身会涉及到 IO 输出,无论是输出到终端或者 error_log,都是如此,所以肯定会影响性能。我们来看看这个影响有多大。
error_reporting(E_ERROR); for($i=0; $i<1000000;$i++) { $str= "通常,$PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。 然而,在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。"; }
在上面的代码中,我们涉及到一个不存在的变量,所以会报出 Notice 错误:
Notice: Undefined variable: PHP 中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的 in xxxx/string2.php on line 10
如果把 E_ ERROR 改成 E_ ALL 就能看到大量的上述错误输出。
我们先执行 E_ ERROR 版,这个时候没有任何错误日志输出。得到如下数据:
[root@localhostphpperf]# time php7 string2.php real 0m0.442s user 0m0.434s sys 0m0.005s [root@localhostphpperf]# time php7 string2.php real 0m0.487s user 0m0.484s sys 0m0.002s [root@localhostphpperf]# time php7 string2.php real 0m0.476s user 0m0.471s sys 0m0.003s
再执行 E_ ALL 版,有大量的错误日志输出,我们把输出重定向到/dev/null
[root@localhostphpperf]# time php7 string2.php > /dev/null real 0m0.928s user 0m0.873s sys 0m0.051s [root@localhostphpperf]# time php7 string2.php > /dev/null real 0m0.984s user 0m0.917s sys 0m0.064s [root@localhostphpperf]# time php7 string2.php > /dev/null real 0m0.945s user 0m0.887s sys 0m0.056s
可见慢了将近一倍。
如上可见,即使输出没有正式写入文件,错误级别打开的影响也是巨大的。在线上我们应该将错误级别调到 E_ ERROR 这个级别,同时将错误写入 error_ log,既减少了不必要的错误信息输出,又避免泄漏路径等信息,造成安全隐患。
2.5、正则表达式和普通字符串操作
在字符串操作中,有一条常见的规则,即是能使用普通字符串操作方法替代的,就不要使用正则表达式来处理,用 C 语言操作 PCRE 做过正则表达式处理的童鞋应该清楚,需要先 compile,再 exec,也就是说是一个相对复杂的过程。现在就比较一下两者的差别。
对于简单的分隔,我们可以使用 explode 来实现,也可以使用正则表达式,比如下面的例子:
ini_set("precision", 16); function microtime_ex() { list($usec, $sec) = explode(" ", microtime()); return $sec+$usec; } for($i=0; $i<1000000; $i++) { microtime_ex(); }
耗时在0.93-1S之间。
[root@localhostphpperf]# time php7 pregstring.php real 0m0.941s user 0m0.931s sys 0m0.007s [root@localhostphpperf]# time php7 pregstring.php real 0m0.986s user 0m0.980s sys 0m0.004s [root@localhostphpperf]# time php7 pregstring.php real 0m1.004s user 0m0.998s sys 0m0.003s
我们再将分隔语句替换成:
list($usec, $sec) = preg_split("#\s#", microtime());
得到如下数据,慢了近10-20%。
[root@localhostphpperf]# time php7 pregstring1.php real 0m1.195s user 0m1.182s sys 0m0.004s [root@localhostphpperf]# time php7 pregstring1.php real 0m1.222s user 0m1.217s sys 0m0.003s [root@localhostphpperf]# time php7 pregstring1.php real 0m1.101s user 0m1.091s sys 0m0.005s
再将语句替换成:
list($usec, $sec) = preg_split("#\s+#", microtime());
即匹配一到多个空格,并没有太多的影响。除了分隔外,查找我们也来看一个例子。
第一段代码:
$str= "China has a Large population"; for($i=0; $i<1000000; $i++) { if(preg_match("#l#i", $str)) { } }
第二段代码:
$str= "China has a large population"; for($i=0; $i<1000000; $i++) { if(stripos($str, "l")!==false) { } }
这两段代码达到的效果相同,都是查找字符串中有无 l 或者 L 字符。
在 PHP 7 下运行效果如下:
[root@localhostphpperf]# time php7 pregstring2.php real 0m0.172s user 0m0.167s sys 0m0.003s [root@localhostphpperf]# time php7 pregstring2.php real 0m0.199s user 0m0.196s sys 0m0.002s [root@localhostphpperf]# time php7 pregstring3.php real 0m0.185s user 0m0.182s sys 0m0.003s [root@localhostphpperf]# time php7 pregstring3.php real 0m0.184s user 0m0.181s sys 0m0.003s
两者区别不大。再看看在 PHP5.6 中的表现。
[root@localhostphpperf]# time php56 pregstring2.php real 0m0.470s user 0m0.456s sys 0m0.004s [root@localhostphpperf]# time php56 pregstring2.php real 0m0.506s user 0m0.500s sys 0m0.005s [root@localhostphpperf]# time php56 pregstring3.php real 0m0.348s user 0m0.342s sys 0m0.004s [root@localhostphpperf]# time php56 pregstring3.php real 0m0.376s user 0m0.364s sys 0m0.003s
可见在 PHP 5.6 中表现还是非常明显的,使用正则表达式慢了20%。PHP7 难道是对已使用过的正则表达式做了缓存?我们调整一下代码如下:
$str= "China has a Large population"; for($i=0; $i<1000000; $i++) { $pattern = "#".chr(ord('a')+$i%26)."#i"; if($ret = preg_match($pattern, $str)!==false) { } }
这是一个动态编译的 pattern。
$str= "China has a large population"; for($i=0; $i<1000000; $i++) { $pattern = "".chr(ord('a')+$i%26).""; if($ret = stripos($str, $pattern)!==false) { } }
在 PHP7 中,得到了如下结果:
[root@localhostphpperf]# time php7 pregstring2.php real 0m0.351s user 0m0.346s sys 0m0.004s [root@localhostphpperf]# time php7 pregstring2.php real 0m0.359s user 0m0.352s sys 0m0.004s [root@localhostphpperf]# time php7 pregstring3.php real 0m0.375s user 0m0.369s sys 0m0.003s [root@localhostphpperf]# time php7 pregstring3.php real 0m0.370s user 0m0.365s sys 0m0.005s
可见两者并不明显。而在 PHP 5.6 中,同样的代码:
[root@localhostphpperf]# time php56 pregstring2.php real 0m1.022s user 0m1.015s sys 0m0.005s [root@localhostphpperf]# time php56 pregstring2.php real 0m1.049s user 0m1.041s sys 0m0.005s [root@localhostphpperf]# time php56 pregstring3.php real 0m0.923s user 0m0.821s sys 0m0.002s [root@localhostphpperf]# time php56 pregstring3.php real 0m0.838s user 0m0.831s sys 0m0.004s
在 PHP 5.6 中,stripos 版明显要快于正则表达式版,由上两例可见,PHP7对正则表达式的优化还是相当惊人的。其次也建议,能用普通字符串操作的地方,可以避免使用正则表达式。因为在其他版本中,这个规则还是适用的。某 zend 大牛官方的分享给出如下数据:
stripos(‘http://’, $website)
速度是preg_match(‘/http:\/\//i’, $website)
的两倍ctype_alnum()
速度是preg_match(‘/^\s*$/’)
的5倍;“if ($test == (int)$test)”
比preg_match(‘/^\d*$/’)
快5倍
可以相见,正则表达式是相对低效的。
2.6、数组元素定位查找
在数组元素的查找中,有一个关键的注意点就是数组值和键的查找速度,差异非常大。了解过 PHP 扩展开发的朋友,应该清楚,数组在底层其实是 Hash 表。所以键是以快速定位的,而值却未必。下面来看例子。
首先们构造一个数组:
$a= array(); for($i=0;$i<100000;$i++){ $a[$i] = $i; }
在这个数组中,我们测试查找值和查找键的效率差别。
第一种方法用 array_ search,第二种用 array_ key_ exists,第三种用 isset 语法结构。 代码分别如下:
//查找值 foreach($a as $i) { array_search($i, $a); } //查找键 foreach($a as $i) { array_key_exists($i, $a); } //判定键是否存在 foreach($a as $i) { if(isset($a[$i])); }
运行结果如下:
[root@localhostphpperf]# time php7 array.php real 0m9.026s user 0m8.965s sys 0m0.007s [root@localhostphpperf]# time php7 array.php real 0m9.063s user 0m8.965s sys 0m0.005s [root@localhostphpperf]# time php7 array1.php real 0m0.018s user 0m0.016s sys 0m0.001s [root@localhostphpperf]# time php7 array1.php real 0m0.021s user 0m0.015s sys 0m0.004s [root@localhostphpperf]# time php7 array2.php real 0m0.020s user 0m0.014s sys 0m0.006s [root@localhostphpperf]# time php7 array2.php real 0m0.016s user 0m0.009s sys 0m0.006s
由上例子可见,键值查找的速度比值查找的速度有百倍以上的效率差别。因而如果能用键值定位的地方,尽量用键值定位,而不是值查找。
2.7、对象与数组
在 PHP 中,数组就是字典,字典可以存储属性和属性值,而且无论是键还是值,都不要求数据类型统一,所以对象数据存储,既能用对象数据结构的属性存储数据,也能使用数组的元素存储数据。那么两者有何差别呢?
使用对象:
classUser { public $uid; public $username; public $age; function getUserInfo() { return "UID:".$this->uid." UserName:".$this->username." Age:".$this->age; } } for($i=0; $i<1000000;$i++) { $user = new User(); $user->uid= $i; $user->age = $i%100; $user->username="User".$i; $user->getUserInfo(); }
使用数组:
functiongetUserInfo($user) { return "UID:".$user['uid']." UserName:".$user['username']." Age:".$user['age']; } for($i=0; $i<1000000;$i++) { $user = array("uid"=>$i,"age" =>$i%100,"username"=>"User".$i); getUserInfo($user); }
我们分别在 PHP5.3、PHP 5.6 和 PHP 7 中运行这两段代码。
[root@localhostphpperf]# time phpobject.php real 0m2.144s user 0m2.119s sys 0m0.009s [root@localhostphpperf]# time phpobject.php real 0m2.106s user 0m2.089s sys 0m0.013s [root@localhostphpperf]# time php object1.php real 0m1.421s user 0m1.402s sys 0m0.016s [root@localhostphpperf]# time php object1.php real 0m1.431s user 0m1.410s sys 0m0.012s
在 PHP 5.3 中,数组版比对象版快了近30%。
[root@localhostphpperf]# time php56 object.php real 0m1.323s user 0m1.319s sys 0m0.002s [root@localhostphpperf]# time php56 object.php real 0m1.414s user 0m1.400s sys 0m0.006s [root@localhostphpperf]# time php56 object1.php real 0m1.356s user 0m1.352s sys 0m0.002s [root@localhostphpperf]# time php56 object1.php real 0m1.364s user 0m1.349s sys 0m0.006s [root@localhostphpperf]# time php7 object.php real 0m0.642s user 0m0.638s sys 0m0.003s [root@localhostphpperf]# time php7 object.php real 0m0.606s user 0m0.602s sys 0m0.003s [root@localhostphpperf]# time php7 object1.php real 0m0.615s user 0m0.613s sys 0m0.000s [root@localhostphpperf]# time php7 object1.php real 0m0.615s user 0m0.611s sys 0m0.003s
到了 PHP 5.6 和 PHP7 中,两个版本基本没有差别,而在 PHP7 中的速度是 PHP5.6 中的2倍。在新的版本中,差别已几乎没有,那么为了清楚起见我们当然应该声明类,实例化类来存储对象数据。
2.8、getter 和 setter
从 Java 转过来学习 PHP 的朋友,在对象声明时,可能习惯使用 getter 和 setter,那么,在 PHP 中,使用 getter 和 setter 是否会带来性能上的损失呢?同样,先上例子。
无 setter版:
classUser { public $uid; public $username; public $age; function getUserInfo() { return "UID:".$this->uid." UserName:".$this->username." Age:".$this->age; } } for($i=0; $i<1000000;$i++) { $user = new User(); $user->uid= $i; $user->age = $i%100; $user->username="User".$i; $user->getUserInfo(); }
有 setter版:
classUser { public $uid; private $username; public $age; function setUserName($name) { $this->username = $name; } function getUserInfo() { return "UID:".$this->uid." UserName:".$this->username." Age:".$this->age; } } for($i=0; $i<1000000;$i++) { $user = new User(); $user->uid= $i; $user->age = $i%100; $user->setUserName("User".$i); $user->getUserInfo(); }
这里只增加了一个 setter。运行结果如下:
[root@localhostphpperf]# time php7 object.php real 0m0.607s user 0m0.602s sys 0m0.004s [root@localhostphpperf]# time php7 object.php real 0m0.598s user 0m0.596s sys 0m0.000s [root@localhostphpperf]# time php7 object2.php real 0m0.673s user 0m0.669s sys 0m0.003s [root@localhostphpperf]# time php7 object2.php real 0m0.668s user 0m0.664s sys 0m0.004s
从上面可以看到,增加了一个 setter,带来了近10%的效率损失。可见这个性能损失是相当大的,在 PHP 中,我们没有必要再来做 setter 和 getter了。需要引用的属性,直接使用即可。
2.9、类属性该声明还是不声明
PHP 本身支持属性可以在使用时增加,也就是不声明属性,可以在运行时添加属性。那么问题来了,事先声明属性与事后增加属性,是否会有性能上的差别。这里也举一个例子探讨一下。
事先声明了属性的代码就是2.8节中,无 setter 的代码,不再重复。而无属性声明的代码如下:
classUser { function getUserInfo() { return "UID:".$this->uid." UserName:".$this->username." Age:".$this->age; } } for($i=0; $i<1000000;$i++) { $user = new User(); $user->uid= $i; $user->age = $i%100; $user->username="User".$i; $user->getUserInfo(); }
两段代码,运行结果如下:
[root@localhostphpperf]# time php7 object.php real 0m0.608s user 0m0.604s sys 0m0.003s [root@localhostphpperf]# time php7 object.php real 0m0.615s user 0m0.605s sys 0m0.003s [root@localhostphpperf]# time php7 object3.php real 0m0.733s user 0m0.728s sys 0m0.004s [root@localhostphpperf]# time php7 object3.php real 0m0.727s user 0m0.720s sys 0m0.004s
从上面的运行可以看到,无属性声明的代码慢了20%。可以推断出来的就是对于对象的属性,如果事先知道的话,我们还是事先声明的好,这一方面是效率问题,另一方面,也有助于提高代码的可读性呢。
2.10、图片操作 API 的效率差别
在图片处理操作中,一个非常常见的操作是将图片缩放成小图。缩放成小图的办法有多种,有使用 API 的,有使用命令行的。在 PHP 中,有 iMagick 和 gmagick 两个扩展可供操作,而命令行则一般使用 convert 命令来处理。我们这里来讨论使用 imagick 扩展中的 API 处理图片的效率差别。
先上代码:
function imagick_resize($filename, $outname) { $thumbnail = new Imagick($filename); $thumbnail->resizeImage(200, 200, imagick::FILTER_LANCZOS, 1); $thumbnail->writeImage($outname); unset($thumbnail); } function imagick_scale($filename, $outname) { $thumbnail = new Imagick($filename); $thumbnail->scaleImage(200, 200); $thumbnail->writeImage($outname); unset($thumbnail); } function convert($func) { $cmd= "find /var/data/ppt |grep jpg"; $start = microtime(true); exec($cmd, $files); $index = 0; foreach($files as $key =>$filename) { $outname= " /tmp/$func"."_"."$key.jpg"; $func($filename, $outname); $index++; } $end = microtime(true); echo "$func $index files: " . ($end- $start) . "s\n"; } convert("imagick_resize"); convert("imagick_scale");
在上面的代码中,我们分别使用了 resizeImage 和 scaleImage 来进行图片的压缩,压缩的是常见的 1-3M 之间的数码相机图片,得到如下运行结果:
[root@localhostphpperf]# php55 imagick.php imagick_ resize 169 files: 5.0612308979034s imagick_ scale 169 files: 3.1105840206146s [root@localhostphpperf]# php55 imagick.php imagick_ resize 169 files: 4.4953861236572s imagick_ scale 169 files: 3.1514940261841s [root@localhostphpperf]# php55 imagick.php imagick_ resize 169 files: 4.5400381088257s imagick_ scale 169 files: 3.2625908851624s
169张图片压缩,使用 resizeImage 压缩,速度在4.5S以上,而使用 scaleImage 则在 3.2S 左右,快了将近50%,压缩的效果,用肉眼看不出明显区别。当然 resizeImage 的控制能力更强,不过对于批量处理而言,使用 scaleImage 是更好的选择,尤其对头像压缩这种频繁大量的操作。本节只是例举了图片压缩 API 作为例子,也正像 explode 和 preg_ split 一样,在 PHP 中,完成同样一件事情,往往有多种手法。建议采用效率高的做法。
以上就是关于 PHP 开发的10个方面的对比,这些点涉及到 PHP 语法、写法以及 API 的使用。有些策略随着 PHP 的发展,有的已经不再适用,有些策略则会一直有用。
有童鞋也许会说,在现实的开发应用中,上面的某些观点和解决策略,有点「然并卵」。为什么这么说呢?因为在一个程序的性能瓶颈中,最为核心的瓶颈,往往并不在 PHP 语言本身。即使是跟 PHP 代码中暴露出来的性能瓶颈,也常在外部资源和程序的不良写法导致的瓶颈上。于是为了做好性能分析,我们需要向 PHP 的上下游戏延伸,比如延伸到后端的服务上去,比如延伸到前端的优化规则。在这两块,都有了相当多的积累和分析,雅虎也据此提出了多达35条前端优化规则,这些同 PHP 本身的性能分析构成了一个整体,就是降低用户的访问延时。
所以前面两部分所述的性能分析,只是有助于大家了解 PHP 开发本身,写出更好的 PHP 程序,为你成为一个资深的 PHP 程序员打下基础,对于实际生产中程序的效率提升,往往帮助也不是特别显著,因为大家也看到,在文章的实例中,很多操作往往是百万次才能看出明显的性能差别。在现实的页面中,每一个请求很快执行完成,对这些基础代码的调用,往往不会有这么多次调用。不过了解这些,总是好的。
那么,对于一个程序而言,其他的性能瓶颈可能存在哪里?我们将深入探讨。所以在本系列的下两篇,我们将探讨 PHP 程序的外围效源的效率问题和前端效率问题,敬请期待。
以上就介绍了PHP 性能分析与实验:性能的微观分析,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PHP 8.4는 상당한 양의 기능 중단 및 제거를 통해 몇 가지 새로운 기능, 보안 개선 및 성능 개선을 제공합니다. 이 가이드에서는 Ubuntu, Debian 또는 해당 파생 제품에서 PHP 8.4를 설치하거나 PHP 8.4로 업그레이드하는 방법을 설명합니다.

숙련된 PHP 개발자라면 이미 그런 일을 해왔다는 느낌을 받을 것입니다. 귀하는 상당한 수의 애플리케이션을 개발하고, 수백만 줄의 코드를 디버깅하고, 여러 스크립트를 수정하여 작업을 수행했습니다.

VS Code라고도 알려진 Visual Studio Code는 모든 주요 운영 체제에서 사용할 수 있는 무료 소스 코드 편집기 또는 통합 개발 환경(IDE)입니다. 다양한 프로그래밍 언어에 대한 대규모 확장 모음을 통해 VS Code는

JWT는 주로 신분증 인증 및 정보 교환을 위해 당사자간에 정보를 안전하게 전송하는 데 사용되는 JSON을 기반으로 한 개방형 표준입니다. 1. JWT는 헤더, 페이로드 및 서명의 세 부분으로 구성됩니다. 2. JWT의 작업 원칙에는 세 가지 단계가 포함됩니다. JWT 생성, JWT 확인 및 Parsing Payload. 3. PHP에서 인증에 JWT를 사용하면 JWT를 생성하고 확인할 수 있으며 사용자 역할 및 권한 정보가 고급 사용에 포함될 수 있습니다. 4. 일반적인 오류에는 서명 검증 실패, 토큰 만료 및 대형 페이로드가 포함됩니다. 디버깅 기술에는 디버깅 도구 및 로깅 사용이 포함됩니다. 5. 성능 최적화 및 모범 사례에는 적절한 시그니처 알고리즘 사용, 타당성 기간 설정 합리적,

이 튜토리얼은 PHP를 사용하여 XML 문서를 효율적으로 처리하는 방법을 보여줍니다. XML (Extensible Markup Language)은 인간의 가독성과 기계 구문 분석을 위해 설계된 다목적 텍스트 기반 마크 업 언어입니다. 일반적으로 데이터 저장 AN에 사용됩니다

문자열은 문자, 숫자 및 기호를 포함하여 일련의 문자입니다. 이 튜토리얼은 다른 방법을 사용하여 PHP의 주어진 문자열의 모음 수를 계산하는 방법을 배웁니다. 영어의 모음은 A, E, I, O, U이며 대문자 또는 소문자 일 수 있습니다. 모음이란 무엇입니까? 모음은 특정 발음을 나타내는 알파벳 문자입니다. 대문자와 소문자를 포함하여 영어에는 5 개의 모음이 있습니다. a, e, i, o, u 예 1 입력 : String = "Tutorialspoint" 출력 : 6 설명하다 문자열의 "Tutorialspoint"의 모음은 u, o, i, a, o, i입니다. 총 6 개의 위안이 있습니다

정적 바인딩 (정적 : :)는 PHP에서 늦은 정적 바인딩 (LSB)을 구현하여 클래스를 정의하는 대신 정적 컨텍스트에서 호출 클래스를 참조 할 수 있습니다. 1) 구문 분석 프로세스는 런타임에 수행됩니다. 2) 상속 관계에서 통화 클래스를 찾아보십시오. 3) 성능 오버 헤드를 가져올 수 있습니다.

PHP의 마법 방법은 무엇입니까? PHP의 마법 방법은 다음과 같습니다. 1. \ _ \ _ Construct, 객체를 초기화하는 데 사용됩니다. 2. \ _ \ _ 파괴, 자원을 정리하는 데 사용됩니다. 3. \ _ \ _ 호출, 존재하지 않는 메소드 호출을 처리하십시오. 4. \ _ \ _ get, 동적 속성 액세스를 구현하십시오. 5. \ _ \ _ Set, 동적 속성 설정을 구현하십시오. 이러한 방법은 특정 상황에서 자동으로 호출되어 코드 유연성과 효율성을 향상시킵니다.
