백엔드 개발 PHP 튜토리얼 PHP 성능 분석 및 실험: 미세한 성능 분석

PHP 성능 분석 및 실험: 미세한 성능 분석

Jul 29, 2016 am 09:15 AM
php root time user

이전 기사에서는 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 性能分析与实验:性能的微观分析

차트는 다음과 같습니다.

PHP 性能分析与实验:性能的微观分析

PHP 5.2에는 적절한 가비지 수집 메커니즘이 없으므로 더 많은 오류가 발생합니다. 그리고 더 많은 메모리 사용량. 5.3 이후에는 메모리 재활용 메커니즘으로 인해 메모리가 일정 범위에서 안정적으로 유지됩니다. 그리고 PHP7이 가장 적은 메모리를 사용한다는 것도 알 수 있습니다. PHP 5.2 그래픽을 제거한 후에는 대비가 더욱 분명해졌습니다.

PHP 性能分析与实验:性能的微观分析

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에서 각각 실행하여 다음과 같은 메모리 사용량 통계표를 얻습니다.

PHP 性能分析与实验:性能的微观分析

图表如下,PHP7 还是内存使用效率最优的。

PHP 性能分析与实验:性能的微观分析

从上面的例子也可以看出来,尽管在第一个例子中,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教程有兴趣的朋友有所帮助。

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

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

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

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

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

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

Ubuntu 및 Debian용 PHP 8.4 설치 및 업그레이드 가이드 Ubuntu 및 Debian용 PHP 8.4 설치 및 업그레이드 가이드 Dec 24, 2024 pm 04:42 PM

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

이전에 몰랐던 후회되는 PHP 함수 7가지 이전에 몰랐던 후회되는 PHP 함수 7가지 Nov 13, 2024 am 09:42 AM

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

PHP 개발을 위해 Visual Studio Code(VS Code)를 설정하는 방법 PHP 개발을 위해 Visual Studio Code(VS Code)를 설정하는 방법 Dec 20, 2024 am 11:31 AM

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

JWT (JSON Web Tokens) 및 PHP API의 사용 사례를 설명하십시오. JWT (JSON Web Tokens) 및 PHP API의 사용 사례를 설명하십시오. Apr 05, 2025 am 12:04 AM

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

PHP에서 HTML/XML을 어떻게 구문 분석하고 처리합니까? PHP에서 HTML/XML을 어떻게 구문 분석하고 처리합니까? Feb 07, 2025 am 11:57 AM

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

문자열로 모음을 계산하는 PHP 프로그램 문자열로 모음을 계산하는 PHP 프로그램 Feb 07, 2025 pm 12:12 PM

문자열은 문자, 숫자 및 기호를 포함하여 일련의 문자입니다. 이 튜토리얼은 다른 방법을 사용하여 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에서 늦은 정적 결합을 설명하십시오 (정적 : :). PHP에서 늦은 정적 결합을 설명하십시오 (정적 : :). Apr 03, 2025 am 12:04 AM

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

php magic 방법 (__construct, __destruct, __call, __get, __set 등)이란 무엇이며 사용 사례를 제공합니까? php magic 방법 (__construct, __destruct, __call, __get, __set 등)이란 무엇이며 사용 사례를 제공합니까? Apr 03, 2025 am 12:03 AM

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

See all articles