国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

PHP 性能分析與實(shí)驗(yàn)(二)——PHP 性能的微觀分析

Airy / 3210人閱讀

摘要:性能分析與實(shí)驗(yàn)性能的宏觀分析在上一篇文章中,我們從是解釋性語言動(dòng)態(tài)語言和底層實(shí)現(xiàn)等三個(gè)方面,探討了性能的問題。在開始分析之前,我們得掌握一些與性能分析相關(guān)的函數(shù)。二性能分析則下面我們根據(jù)小程序來驗(yàn)證一些常見的性能差別。

【編者按】此前,閱讀過了很多關(guān)于 PHP 性能分析的文章,不過寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語法要點(diǎn)。本文就改變 PHP 性能分析的角度,并通過實(shí)例來分析出 PHP 的性能方面需要注意和改進(jìn)的點(diǎn)。

PHP 性能分析與實(shí)驗(yàn)——性能的宏觀分析

在上一篇文章中,我們從 PHP 是解釋性語言、動(dòng)態(tài)語言和底層實(shí)現(xiàn)等三個(gè)方面,探討了 PHP 性能的問題。本文就深入到 PHP 的微觀層面,我們來了解 PHP 在使用和編寫代碼過程中,性能方面,可能需要注意和提升的地方。

在開始分析之前,我們得掌握一些與性能分析相關(guān)的函數(shù)。這些函數(shù)讓我們對(duì)程序性能有更好的分析和評(píng)測(cè)。

一、性能分析相關(guān)的函數(shù)與命令 1.1、時(shí)間度量函數(shù)

平時(shí)我們常用 time() 函數(shù),但是返回的是秒數(shù),對(duì)于某段代碼的內(nèi)部性能分析,到秒的精度是不夠的。于是要用 microtime 函數(shù)。而 microtime 函數(shù)可以返回兩種形式,一是字符串的形式,一是浮點(diǎn)數(shù)的形式。不過需要注意的是,在缺省的情況下,返回的精度只有4位小數(shù)。為了獲得更高的精確度,我們需要配置 precision。

如下是 microtime 的使用結(jié)果。

    $start= microtime(true);
    echo $start."
";
    $end = microtime(true);
    echo $end."
";
    echo ($end-$start)."
";

輸出為:

    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

除了 microtime 內(nèi)部統(tǒng)計(jì)之外, 還可以使用 getrusage 來取得用戶態(tài)的事長。在實(shí)際的操作中,也常用 time 命令來計(jì)算整個(gè)程序的運(yùn)行時(shí)長,通過多次運(yùn)行或者修改代碼后運(yùn)行,得到不同的時(shí)間長度以得到效率上的區(qū)別。 具體用法是:time phptime.php ,則在程序運(yùn)行完成之后,不管是否正常結(jié)束退出,都會(huì)有相關(guān)的統(tǒng)計(jì)。

    bash-3.2# time phptime.php

    1441360373.150756
    1441360373.150959
    0.0002031326293945312

    real    0m0.186s
    user    0m0.072s
    sys     0m0.077s

因?yàn)楸疚乃懻摰男阅軉栴},往往分析上百萬次調(diào)用之后的差距與趨勢(shì),為了避免代碼中存在一些時(shí)間統(tǒng)計(jì)代碼,后面我們使用 time 命令居多。

1.2、內(nèi)存使用相關(guān)函數(shù)

分析內(nèi)存使用的函數(shù)有兩個(gè):memory_ get_ usage、memory_ get_ peak_usage,前者可以獲得程序在調(diào)用的時(shí)間點(diǎn),即當(dāng)前所使用的內(nèi)存,后者可以獲得到目前為止高峰時(shí)期所使用的內(nèi)存。所使用的內(nèi)存以字節(jié)為單位。

    $base_memory= memory_get_usage();
    echo "Hello,world!
";
    $end_memory= memory_get_usage();
    $peak_memory= memory_get_peak_usage();

    echo $base_memory,"	",$end_memory,"	",($end_memory-$base_memory),"	", $peak_memory,"
";

輸出如下:

    bash-3.2# phphelloworld.php

    Hello,world!
    224400  224568  168     227424

可以看到,即使程序中間只輸出了一句話,再加上變量存儲(chǔ),也消耗了168個(gè)字節(jié)的內(nèi)存。

對(duì)于同一程序,不同 PHP 版本對(duì)內(nèi)存的使用并不相同,甚至還差別很大。

    $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
";
        }
    }
    echo "  peak: ",memory_get_peak_usage(true), " bytes
";

在 PHP 5.2 中,內(nèi)存使用如下:

    [root@localhostphpperf]# php52 memory.php


    0: 93784 bytes
    10000: 93784 bytes
    ……
    80000: 93784 bytes
    90000: 93784 bytes
    peak: 262144 bytes

PHP 5.3 中,內(nèi)存使用如下

    [root@localhostphpperf]# phpmemory.php


    0: 634992 bytes
    10000: 634992 bytes
    ……
    80000: 634992 bytes
    90000: 634992 bytes
    peak: 786432 bytes

可見 PHP 5.3 在內(nèi)存使用上要粗放了一些。

PHP 5.4 - 5.6 差不多,有所優(yōu)化:

    [root@localhostphpperf]# php56 memory.php


    0: 224944 bytes
    10000: 224920 bytes
    ……
    80000: 224920 bytes
    90000: 224920 bytes
    peak: 262144 bytes

而 PHP 7 在少量使用時(shí),高峰內(nèi)存的使用,增大很多。

    [root@localhostphpperf]# php7 memory.php


    0: 353912 bytes
    10000: 353912 bytes
    ……
    80000: 353912 bytes
    90000: 353912 bytes
    peak: 2097152 bytes

從上面也看到,以上所使用的 PHP 都有比較好的垃圾回收機(jī)制,10萬次初始化,并沒有隨著對(duì)象初始化的增多而增加內(nèi)存的使用。PHP7 的高峰內(nèi)存使用最多,達(dá)到了接近 2M。

下面再來看一個(gè)例子,在上面的代碼的基礎(chǔ)上,我們加上一行,即如下加粗的一行:

    $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
";
        }
    }
    echo "  peak: ",memory_get_peak_usage(true), " bytes
";

這時(shí)候再來看看內(nèi)存的使用情況,中間表格主體部分為內(nèi)存使用量,單位為字節(jié)。

圖表如下:

PHP 5.2 并沒有合適的垃圾回收機(jī)制,導(dǎo)致內(nèi)存使用越來越多。而5.3 以后內(nèi)存回收機(jī)制導(dǎo)致內(nèi)存穩(wěn)定在一個(gè)區(qū)間。而也可以看見 PHP7 內(nèi)存使用最少。把 PHP 5.2 的圖形去掉了之后,對(duì)比更為明顯。

可見 PHP7 不僅是在算法效率上,有大幅度的提升,在大批量內(nèi)存使用上也有大幅度的優(yōu)化(盡管小程序的高峰內(nèi)存比歷史版本所用內(nèi)存更多)。

1.3、垃圾回收相關(guān)函數(shù)

在 PHP 中,內(nèi)存回收是可以控制的,我們可以顯式地關(guān)閉或者打開垃圾回收,一種方法是通過修改配置,zend.enable_gc=Off 就可以關(guān)掉垃圾回收。缺省情況下是 On 的。另外一種手段是通過 gc _enable()和gc _disable()函數(shù)分別打開和關(guān)閉垃圾回收。

比如在上面的例子的基礎(chǔ)上,我們關(guān)閉垃圾回收,就可以得到如下數(shù)據(jù)表格和圖表。

代碼如下:

    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
";
        }
    }
    echo "  peak: ",memory_get_peak_usage(true), " bytes
";

分別在 PHP 5.3、PHP5.4 、PHP5.5、PHP5.6 、PHP7 下運(yùn)行,得到如下內(nèi)存使用統(tǒng)計(jì)表。

圖表如下,PHP7 還是內(nèi)存使用效率最優(yōu)的。

從上面的例子也可以看出來,盡管在第一個(gè)例子中,PHP7 的高峰內(nèi)存使用數(shù)是最多的,但是當(dāng)內(nèi)存使用得多時(shí),PHP7 的內(nèi)存優(yōu)化就體現(xiàn)出來了。

這里值得一提的是垃圾回收,盡管會(huì)使內(nèi)存減少,但是會(huì)導(dǎo)致速度降低,因?yàn)槔厥找彩切枰?CPU 等其他系統(tǒng)資源的。Composer 項(xiàng)目就曾經(jīng)因?yàn)樵谟?jì)算依賴前關(guān)閉垃圾回收,帶來成倍性能提升,引發(fā)廣大網(wǎng)友關(guān)注。詳見:

https://github.com/composer/composer/commit/ac676f47f7bbc619678a29deae097b6b0710b799

在常見的代碼和性能分析中,出了以上三類函數(shù)之外,還常使用的有堆棧跟蹤函數(shù)、輸出函數(shù),這里不再贅述。

二、PHP 性能分析10則

下面我們根據(jù)小程序來驗(yàn)證一些常見的性能差別。

2.1、使用 echo 還是 print

在有的建議規(guī)則中,會(huì)建議使用 echo ,而不使用 print。說 print 是函數(shù),而 echo 是語法結(jié)構(gòu)。實(shí)際上并不是如此,print 也是語法結(jié)構(gòu),類似的語法結(jié)構(gòu),還有多個(gè),比如 list、isset、require 等。不過對(duì)于 PHP 7 以下 PHP 版本而言,兩者確實(shí)有性能上的差別。如下兩份代碼:

    for($i=0; $i<1000000; $i++)
    {
    echo("Hello,World!");
    }
    
    for($i=0; $i<1000000; $i++)
    {
    print ("Hello,World!");
    }

在 PHP 5.3 中運(yùn)行速度分別如下(各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 以上的版本中,區(qū)別不大,如下是 PHP7 中的運(yùn)行效率。

    [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

正如瀏覽器前端的一些優(yōu)化準(zhǔn)則一樣,沒有啥特別通用的原則,往往根據(jù)不同的情況和版本,規(guī)則也會(huì)存在不同。

2.2、require 還是 require_once?

在一些常規(guī)的優(yōu)化規(guī)則中,會(huì)提到,建議使用 require_ once 而不是 require,現(xiàn)由是 require_ once 會(huì)去檢測(cè)是否重復(fù),而 require 則不需要重復(fù)檢測(cè)。

在大量不同文件的包含中,require_ once 略慢于 require。但是 require_ once 的檢測(cè)是一項(xiàng)內(nèi)存中的行為,也就是說即使有數(shù)個(gè)需要加載的文件,檢測(cè)也只是內(nèi)存中的比較。而 require 的每次重新加載,都會(huì)從文件系統(tǒng)中去讀取分析。因而 require_ once 會(huì)比 require 更佳。咱們也使用一個(gè)例子來看一下。

    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 的運(yùn)行速度是 require.php 的30倍!在其他版本也能得到大致相同的結(jié)果。

    [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

從上可以看到,如果存在大量的重復(fù)加載的話,require_ once 明顯優(yōu)于 require,因?yàn)橹貜?fù)的文件不再有 IO 操作。即使不是大量重復(fù)的加載,也建議使用 require_ once,因?yàn)樵谝粋€(gè)程序中,一般不會(huì)存在數(shù)以千百計(jì)的文件包含,100次內(nèi)存比較的速度差距,一個(gè)文件包含就相當(dāng)了。

2.3、單引號(hào)還是雙引號(hào)?

單引號(hào),還是雙引號(hào),是一個(gè)問題。一般的建議是能使用單引號(hào)的地方,就不要使用雙引號(hào),因?yàn)樽址械膯我?hào),不會(huì)引起解析,從而效率更高。那來看一下實(shí)際的差別。

    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 類中,有四個(gè)不同的方法,完成一樣的功能,就是拼接信息返回,看看這四個(gè)不同的方法的區(qū)別。

第一個(gè)、getUserInfo ,使用雙引號(hào)和屬性相拼接

    [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

第二個(gè)、getUserInfoSingle ,使用單引號(hào)和屬性相拼接

    [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

可見在拼接中,單雙引號(hào)并無明顯差別。

第三個(gè)、getUserInfoOnce,不再使用句號(hào).連接,而是直接引入在字符串中解析。

    [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%的效率提升。可見連綴效率更低一些。

第四個(gè)、getUserInfoSingle2 雖然沒有達(dá)到我們真正想要的效果,功能是不正確的,但是在字符串中,不再需要解析變量和獲取變量值,所以效率確實(shí)有大幅度提升。

    [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

效率確實(shí)有了大的提升,快了50%。

那么這個(gè)快,是由于不需要變量引用解析帶來的,還是只要加入$天然的呢?我們?cè)僭囍鴮懥艘粋€(gè)方法。

    functiongetUserInfoSingle3()
    {
    return "UID:{$this->uid} UserName:{$this->username} Age:{$this->age}";
    }

得到如下運(yùn)行時(shí)間:

    [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

發(fā)現(xiàn)轉(zhuǎn)義后的字符串,效率跟單引號(hào)是一致的,從這里也可以看見,單引號(hào)還是雙引號(hào)包含,如果不存在需要解析的變量,幾乎沒有差別。如果有需要解析的變量,你也不能光用單引號(hào),要么使用單引號(hào)和連綴,要么使用內(nèi)部插值,所以在這條規(guī)則上,不用太過糾結(jié)。

2.4、錯(cuò)誤應(yīng)該打開還是關(guān)閉?

在 PHP 中,有多種錯(cuò)誤消息,錯(cuò)誤消息的開啟是否會(huì)帶來性能上的影響呢?從直覺覺得,由于錯(cuò)誤消息,本身會(huì)涉及到 IO 輸出,無論是輸出到終端或者 error_log,都是如此,所以肯定會(huì)影響性能。我們來看看這個(gè)影響有多大。

    error_reporting(E_ERROR);
    for($i=0; $i<1000000;$i++) {
    $str= "通常,$PHP中的垃圾回收機(jī)制,僅僅在循環(huán)回收算法確實(shí)運(yùn)行時(shí)會(huì)有時(shí)間消耗上的增加。但是在平常的(更小的)腳本中應(yīng)根本就沒有性能影響。
    然而,在平常腳本中有循環(huán)回收機(jī)制運(yùn)行的情況下,內(nèi)存的節(jié)省將允許更多這種腳本同時(shí)運(yùn)行在你的服務(wù)器上。因?yàn)榭偣彩褂玫膬?nèi)存沒達(dá)到上限。";
    }

在上面的代碼中,我們涉及到一個(gè)不存在的變量,所以會(huì)報(bào)出 Notice 錯(cuò)誤:

Notice: Undefined variable: PHP 中的垃圾回收機(jī)制,僅僅在循環(huán)回收算法確實(shí)運(yùn)行時(shí)會(huì)有時(shí)間消耗上的增加。但是在平常的 in xxxx/string2.php on line 10

如果把 E_ ERROR 改成 E_ ALL 就能看到大量的上述錯(cuò)誤輸出。

我們先執(zhí)行 E_ ERROR 版,這個(gè)時(shí)候沒有任何錯(cuò)誤日志輸出。得到如下數(shù)據(jù):

    [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

再執(zhí)行 E_ ALL 版,有大量的錯(cuò)誤日志輸出,我們把輸出重定向到/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

可見慢了將近一倍。

如上可見,即使輸出沒有正式寫入文件,錯(cuò)誤級(jí)別打開的影響也是巨大的。在線上我們應(yīng)該將錯(cuò)誤級(jí)別調(diào)到 E_ ERROR 這個(gè)級(jí)別,同時(shí)將錯(cuò)誤寫入 error_ log,既減少了不必要的錯(cuò)誤信息輸出,又避免泄漏路徑等信息,造成安全隱患。

2.5、正則表達(dá)式和普通字符串操作

在字符串操作中,有一條常見的規(guī)則,即是能使用普通字符串操作方法替代的,就不要使用正則表達(dá)式來處理,用 C 語言操作 PCRE 做過正則表達(dá)式處理的童鞋應(yīng)該清楚,需要先 compile,再 exec,也就是說是一個(gè)相對(duì)復(fù)雜的過程。現(xiàn)在就比較一下兩者的差別。

對(duì)于簡單的分隔,我們可以使用 explode 來實(shí)現(xiàn),也可以使用正則表達(dá)式,比如下面的例子:

    ini_set("precision", 16);
    function microtime_ex()
    {
    list($usec, $sec) = explode(" ", microtime());
    return $sec+$usec;
    }
    
    for($i=0; $i<1000000; $i++) {
    microtime_ex();
    }

耗時(shí)在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

我們?cè)賹⒎指粽Z句替換成:

    list($usec, $sec) = preg_split("#s#", microtime());

得到如下數(shù)據(jù),慢了近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());

即匹配一到多個(gè)空格,并沒有太多的影響。除了分隔外,查找我們也來看一個(gè)例子。

第一段代碼:

    $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)
        {
        }
    }

這兩段代碼達(dá)到的效果相同,都是查找字符串中有無 l 或者 L 字符。

在 PHP 7 下運(yùn)行效果如下:

    [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

兩者區(qū)別不大。再看看在 PHP5.6 中的表現(xiàn)。

    [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 中表現(xiàn)還是非常明顯的,使用正則表達(dá)式慢了20%。PHP7 難道是對(duì)已使用過的正則表達(dá)式做了緩存?我們調(diào)整一下代碼如下:

    $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)
        {
        }
   }

這是一個(gè)動(dòng)態(tài)編譯的 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 中,得到了如下結(jié)果:

    [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 版明顯要快于正則表達(dá)式版,由上兩例可見,PHP7對(duì)正則表達(dá)式的優(yōu)化還是相當(dāng)驚人的。其次也建議,能用普通字符串操作的地方,可以避免使用正則表達(dá)式。因?yàn)樵谄渌姹局校@個(gè)規(guī)則還是適用的。某 zend 大牛官方的分享給出如下數(shù)據(jù):

stripos(‘http://’, $website) 速度是preg_match(‘/http:///i’, $website) 的兩倍

ctype_alnum()速度是preg_match(‘/^s*$/’)的5倍;

“if ($test == (int)$test)”preg_match(‘/^d*$/’)快5倍

可以相見,正則表達(dá)式是相對(duì)低效的。

2.6、數(shù)組元素定位查找

在數(shù)組元素的查找中,有一個(gè)關(guān)鍵的注意點(diǎn)就是數(shù)組值和鍵的查找速度,差異非常大。了解過 PHP 擴(kuò)展開發(fā)的朋友,應(yīng)該清楚,數(shù)組在底層其實(shí)是 Hash 表。所以鍵是以快速定位的,而值卻未必。下面來看例子。

首先們構(gòu)造一個(gè)數(shù)組:

    $a= array();
    for($i=0;$i<100000;$i++){
    $a[$i] = $i;
    }

在這個(gè)數(shù)組中,我們測(cè)試查找值和查找鍵的效率差別。

第一種方法用 array_ search,第二種用 array_ key_ exists,第三種用 isset 語法結(jié)構(gòu)。
代碼分別如下:

    //查找值
    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]));
    }

運(yùn)行結(jié)果如下:

    [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、對(duì)象與數(shù)組

在 PHP 中,數(shù)組就是字典,字典可以存儲(chǔ)屬性和屬性值,而且無論是鍵還是值,都不要求數(shù)據(jù)類型統(tǒng)一,所以對(duì)象數(shù)據(jù)存儲(chǔ),既能用對(duì)象數(shù)據(jù)結(jié)構(gòu)的屬性存儲(chǔ)數(shù)據(jù),也能使用數(shù)組的元素存儲(chǔ)數(shù)據(jù)。那么兩者有何差別呢?

使用對(duì)象:

    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();
    }

使用數(shù)組:

    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 中運(yùn)行這兩段代碼。

    [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 中,數(shù)組版比對(duì)象版快了近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 中,兩個(gè)版本基本沒有差別,而在 PHP7 中的速度是 PHP5.6 中的2倍。在新的版本中,差別已幾乎沒有,那么為了清楚起見我們當(dāng)然應(yīng)該聲明類,實(shí)例化類來存儲(chǔ)對(duì)象數(shù)據(jù)。

2.8、getter 和 setter

從 Java 轉(zhuǎn)過來學(xué)習(xí) PHP 的朋友,在對(duì)象聲明時(shí),可能習(xí)慣使用 getter 和 setter,那么,在 PHP 中,使用 getter 和 setter 是否會(huì)帶來性能上的損失呢?同樣,先上例子。

無 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();
    }

這里只增加了一個(gè) setter。運(yùn)行結(jié)果如下:

    [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

從上面可以看到,增加了一個(gè) setter,帶來了近10%的效率損失。可見這個(gè)性能損失是相當(dāng)大的,在 PHP 中,我們沒有必要再來做 setter 和 getter了。需要引用的屬性,直接使用即可。

2.9、類屬性該聲明還是不聲明

PHP 本身支持屬性可以在使用時(shí)增加,也就是不聲明屬性,可以在運(yùn)行時(shí)添加屬性。那么問題來了,事先聲明屬性與事后增加屬性,是否會(huì)有性能上的差別。這里也舉一個(gè)例子探討一下。

事先聲明了屬性的代碼就是2.8節(jié)中,無 setter 的代碼,不再重復(fù)。而無屬性聲明的代碼如下:

    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();
    }

兩段代碼,運(yùn)行結(jié)果如下:

    [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

從上面的運(yùn)行可以看到,無屬性聲明的代碼慢了20%。可以推斷出來的就是對(duì)于對(duì)象的屬性,如果事先知道的話,我們還是事先聲明的好,這一方面是效率問題,另一方面,也有助于提高代碼的可讀性呢。

2.10、圖片操作 API 的效率差別

在圖片處理操作中,一個(gè)非常常見的操作是將圖片縮放成小圖。縮放成小圖的辦法有多種,有使用 API 的,有使用命令行的。在 PHP 中,有 imagick 和 gmagick 兩個(gè)擴(kuò)展可供操作,而命令行則一般使用 convert 命令來處理。我們這里來討論使用 imagick 擴(kuò)展中的 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
";
    }
    
    convert("imagick_resize");
    convert("imagick_scale");

在上面的代碼中,我們分別使用了 resizeImage 和 scaleImage 來進(jìn)行圖片的壓縮,壓縮的是常見的 1-3M 之間的數(shù)碼相機(jī)圖片,得到如下運(yùn)行結(jié)果:

    [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%,壓縮的效果,用肉眼看不出明顯區(qū)別。當(dāng)然 resizeImage 的控制能力更強(qiáng),不過對(duì)于批量處理而言,使用 scaleImage 是更好的選擇,尤其對(duì)頭像壓縮這種頻繁大量的操作。本節(jié)只是例舉了圖片壓縮 API 作為例子,也正像 explode 和 preg_ split 一樣,在 PHP 中,完成同樣一件事情,往往有多種手法。建議采用效率高的做法。

以上就是關(guān)于 PHP 開發(fā)的10個(gè)方面的對(duì)比,這些點(diǎn)涉及到 PHP 語法、寫法以及 API 的使用。有些策略隨著 PHP 的發(fā)展,有的已經(jīng)不再適用,有些策略則會(huì)一直有用。

有童鞋也許會(huì)說,在現(xiàn)實(shí)的開發(fā)應(yīng)用中,上面的某些觀點(diǎn)和解決策略,有點(diǎn)「然并卵」。為什么這么說呢?因?yàn)樵谝粋€(gè)程序的性能瓶頸中,最為核心的瓶頸,往往并不在 PHP 語言本身。即使是跟 PHP 代碼中暴露出來的性能瓶頸,也常在外部資源和程序的不良寫法導(dǎo)致的瓶頸上。于是為了做好性能分析,我們需要向 PHP 的上下游戲延伸,比如延伸到后端的服務(wù)上去,比如延伸到前端的優(yōu)化規(guī)則。在這兩塊,都有了相當(dāng)多的積累和分析,雅虎也據(jù)此提出了多達(dá)35條前端優(yōu)化規(guī)則,這些同 PHP 本身的性能分析構(gòu)成了一個(gè)整體,就是降低用戶的訪問延時(shí)。

所以前面兩部分所述的性能分析,只是有助于大家了解 PHP 開發(fā)本身,寫出更好的 PHP 程序,為你成為一個(gè)資深的 PHP 程序員打下基礎(chǔ),對(duì)于實(shí)際生產(chǎn)中程序的效率提升,往往幫助也不是特別顯著,因?yàn)榇蠹乙部吹剑谖恼碌膶?shí)例中,很多操作往往是百萬次才能看出明顯的性能差別。在現(xiàn)實(shí)的頁面中,每一個(gè)請(qǐng)求很快執(zhí)行完成,對(duì)這些基礎(chǔ)代碼的調(diào)用,往往不會(huì)有這么多次調(diào)用。不過了解這些,總是好的。

那么,對(duì)于一個(gè)程序而言,其他的性能瓶頸可能存在哪里?我們將深入探討。所以在本系列的下兩篇,我們將探討 PHP 程序的外圍效源的效率問題和前端效率問題,敬請(qǐng)期待。

PHP 性能分析與實(shí)驗(yàn)——性能的宏觀分析

本文系 |c51143676ca57db0bcf4c7cc254287ca26| 工程師編譯整理。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方博客。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/21099.html

相關(guān)文章

  • PHP 開發(fā)中外圍資源性能分析(一)

    摘要:而是說,程序的外部資源,往往是影響性能的重要因素,尤其是當(dāng)外部資源的連接和數(shù)據(jù)獲取本身速度達(dá)不到理想的結(jié)果時(shí)。 暫且不討論「PHP 是不是最好的編程語言」,本文我們將分別分析一下在 PHP 程序的后端外圍資源和前端外圍資源,它們對(duì)整個(gè) PHP Web 應(yīng)用體驗(yàn)的影響,這往往比語言本身大得多。 首先,后端外圍資源,是指跟 PHP 運(yùn)行過程中與語言本身無關(guān)的網(wǎng)絡(luò)與 IO 操作、存儲(chǔ)服務(wù)、中...

    anquan 評(píng)論0 收藏0
  • PHP 性能分析實(shí)驗(yàn)——性能宏觀分析

    摘要:本文就改變性能分析的角度,并通過實(shí)例來分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語言的執(zhí)行過程。這里分別啟用和做實(shí)驗(yàn)。 編者按】此前,閱讀過了很多關(guān)于 PHP 性能分析的文章,不過寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語法要點(diǎn)。本文就改變 PHP 性能分析的角度,并通過實(shí)例來分析出 PHP 的性能方面...

    anquan 評(píng)論0 收藏0
  • PHP 性能分析實(shí)驗(yàn)——性能宏觀分析

    摘要:本文就改變性能分析的角度,并通過實(shí)例來分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語言的執(zhí)行過程。這里分別啟用和做實(shí)驗(yàn)。 【編者按】此前,閱讀過了很多關(guān)于 PHP 性能分析的文章,不過寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語法要點(diǎn)。本文就改變 PHP 性能分析的角度,并通過實(shí)例來分析出 PHP 的性能方...

    yy736044583 評(píng)論0 收藏0
  • PHP 性能分析實(shí)驗(yàn)——性能宏觀分析

    摘要:本文就改變性能分析的角度,并通過實(shí)例來分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語言的執(zhí)行過程。這里分別啟用和做實(shí)驗(yàn)。 此前,閱讀過了很多關(guān)于 PHP 性能分析的文章,不過寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語法要點(diǎn)。本文就改變PHP 性能分析的角度,并通過實(shí)例來分析出 PHP 的性能方面需要注意和...

    ZHAO_ 評(píng)論0 收藏0
  • PHP 性能分析實(shí)驗(yàn)——性能宏觀分析

    摘要:本文就改變性能分析的角度,并通過實(shí)例來分析出的性能方面需要注意和改進(jìn)的點(diǎn)。如下是作為解釋性語言的執(zhí)行過程。這里分別啟用和做實(shí)驗(yàn)。 此前,閱讀過了很多關(guān)于 PHP 性能分析的文章,不過寫的都是一條一條的規(guī)則,而且,這些規(guī)則并沒有上下文,也沒有明確的實(shí)驗(yàn)來體現(xiàn)出這些規(guī)則的優(yōu)勢(shì),同時(shí)討論的也側(cè)重于一些語法要點(diǎn)。本文就改變PHP 性能分析的角度,并通過實(shí)例來分析出 PHP 的性能方面需要注意和...

    Ilikewhite 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<