首頁  >  文章  >  後端開發  >  PHP uniqid函數執行緩慢的問題

PHP uniqid函數執行緩慢的問題

巴扎黑
巴扎黑原創
2016-11-12 10:23:331231瀏覽

前段時間某個需求:客戶提交簡單的表單可以創建一個適應於全終端(PC,Pad,Phone)的刮刮卡活動H5頁面,其中涉及到客戶可在線生成限額6W獎品碼的功能。

因為需要保持每個活動獎品碼的唯一,我們先是準備用PHP的uniqid函數來生成UUID(Universally Unique IDentifier,也叫GUID,為全局唯一標識符,是一種由算法生成的唯一標識)來生成。

但當我們用生成1W測試時候,發現生成過些需要幾十秒,還不包括插入至資料庫的時間,然後用xhprof寫了個簡單例子進行效能測試   

<?php  
  
xhprof_enable(XHPROF_FLAGS_CPU|XHPROF_FLAGS_MEMORY);  
  
function   myfunc(){  
  
    for($i=0;$i<10000;$i++){  
  
        $data = uniqid();  
    }  
}  
  
myfunc();  
  
$data = xhprof_disable();  
  
print_r($data);

測試結果: 

[myfunc==>uniqid] => Array(  
            [ct] => 10000  
            [wt] => 39975062  
            [cpu] => 0  
            [mu] => 960752  
            [pmu] => 0  
)

竟然需要接近40秒的時間生成,單次執行需要3969微秒,也即是0.003969秒生成。如果用戶提交表單的同時生成兌換碼,最壞情況下需要4分鐘才給用戶反應,當然可以用訊息隊列異步生成,但是為啥uniqid需要如此多的時間來生成一個簡單的字串呢?

接著去查看uniqid的實作源碼,程式碼貼在下面 

PHP_FUNCTION(uniqid)  
{  
     char *prefix = "";  
#if defined(__CYGWIN__)  
     zend_bool more_entropy = 1;  
#else  
     zend_bool more_entropy = 0;  
#endif  
     char *uniqid;  
     int sec, usec, prefix_len = 0;  
     struct timeval tv;  
  
     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sb", &prefix, &prefix_len,  
                                     &more_entropy)) {  
          return;  
     }  
  
#if HAVE_USLEEP && !defined(PHP_WIN32)  
     if (!more_entropy) {  
#if defined(__CYGWIN__)  
          php_error_docref(NULL TSRMLS_CC, E_WARNING, "You must use &#39;more entropy&#39; under CYGWIN");  
          RETURN_FALSE;  
#else  
          usleep(1);  
#endif  
     }  
#endif  
     gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);  
     sec = (int) tv.tv_sec;  
     usec = (int) (tv.tv_usec % 0x100000);  
  
     /* The max value usec can have is 0xF423F, so we use only five hex 
     * digits for usecs. 
     */  
     if (more_entropy) {  
          spprintf(&uniqid, 0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);  
     } else {  
          spprintf(&uniqid, 0, "%s%08x%05x", prefix, sec, usec);  
     }  
  
     RETURN_STRING(uniqid, 0);  
}

看邏輯也沒有複雜的操作,也是對現在時間秒和微秒進行簡單的處理,然後寫了個簡單的測試 

int getUniqid( char * uid) {  
     int sec, usec;  
     struct timeval tv;  
     gettimeofday(( struct timeval *) &tv, ( struct timezone *) NULL);  
     sec = ( int) tv. tv_sec;  
     usec = ( int ) (tv.tv_usec % 0x100000);  
     sprintf(uid, "%08x%05x" , sec, usec);  
     return 1;  
}

執行1W次也就需要2000微秒,這又是為啥?但是我們發現生成的uid中存在大量的重複,這是才注意點原代碼中的usleep函數, 
加上usleep函數在測試,這次和PHP結果一致也需要將近40秒,usleep在此為了保持每次生成的uid不同。
那問題出現在usleep函數上了,然後在usleep前後加上取間隔時間,代碼如下 

struct timeval start, end;  
     gettimeofday(( struct timeval *) &start, ( struct timezone *) NULL);  
     usleep(1);  
     gettimeofday(( struct timeval *) &end, ( struct timezone *) NULL);  
     unsigned long space = (end.tv_sec - start. tv_sec) * 1000000 + end.tv_usec  
             - start. tv_usec;  
      spaceCost += space;

最後發現生成1W獎品碼需要39.99587739.995877秒,而usleep區間隔時間總和39.9824442秒,而usleepusepleep發現每次usleep(1)從進程掛起到喚醒需要4000微秒,本來就知道usleep達不到精度,也也相差太遠了點。 
最後用了下面程式碼產生獎品碼 

   

/** 
     * 生成兑换码并保存到数据库  返回setNo 
     * $pageId 活动ID 
     * $level 奖品等级 
     * $numbers 生成奖品的个数 
     */  
    public static  function generateCDKEYAndSave($pageId,$level,$numbers){  
  
        $level1Prefix =array(2,5,9,&#39;E&#39;,&#39;F&#39;,&#39;M&#39;,&#39;N&#39;,&#39;Q&#39;,&#39;K&#39;,&#39;Z&#39;);//一等奖的前缀  
  
        $level2Prefix =array(1,3,7,&#39;A&#39;,&#39;C&#39;,&#39;J&#39;,&#39;R&#39;,&#39;U&#39;,&#39;V&#39;,&#39;X&#39;);//二等奖的前置  
  
        $level3Prefix = array(4,6,8,&#39;B&#39;,&#39;D&#39;,&#39;G&#39;,&#39;H&#39;,&#39;I&#39;,&#39;L&#39;,&#39;O&#39;,&#39;P&#39;,&#39;R&#39;,&#39;S&#39;,&#39;T&#39;,&#39;W&#39;,&#39;Y&#39;);//三等奖的前缀  
  
        if(empty($pageId) || empty($level) || empty($numbers)) return false;  
  
        $levelPrefix =$level1Prefix;  
  
        if($level==2) $levelPrefix = $level2Prefix;  
  
        if($level==3) $levelPrefix = $level3Prefix;  
  
        $codes =array();  
  
        $now = time();  
  
        for($i=0;$i<$numbers;$i++){  
  
            $prefixKey = array_rand($levelPrefix);  
  
            $prefix = self::COUPON_PREFIX.$levelPrefix[$prefixKey];  
  
            //$code =base_convert(hexdec(md5(uniqid())),10,26); 服务器上面uniqid执行慢的要死  
  
            //$code =base_convert(hexdec(md5($pageId.&#39;A#1$v&&#39;.$i)),10,26);//数据过多 hexdec丢失大量精度  
  
            $code1 = base_convert(substr(md5($pageId.$i.$now), 0, 10), 16, 36);  
  
            $code2 = base_convert($i, 10, 26);  
  
            $code2Len = strlen($code2);  
  
            if ($code2Len == 1) {  
                $code2 .= chr(rand(82, 90)) . chr(rand(82, 90)) . chr(rand(82, 90));  
            }  
            else if ($code2Len == 2) {  
                $code2 .= chr(rand(82, 90)) . chr(rand(82, 90));  
            }  
            else if ($code2Len == 3) {  
                $code2 .= chr(rand(82, 90));  
            }  
  
            $code =$code1.$code2;  
  
            $codes[] = $prefix.strtoupper($code);  
  
        }  
  
       return $codes;  
  
    }

附帶uuid測試程式碼   

#include <stdio.h>  
#include <malloc.h>  
#include <sys/time.h>  
#include <unistd.h>  
  
unsigned long sleepCost = 0;  
  
int getUniqid( char * uid,int times) {  
  
      struct timeval start, end;  
  
      gettimeofday(( struct timeval *) &start, ( struct timezone *) NULL);  
  
      usleep(1);  
  
      gettimeofday(( struct timeval *) &end, ( struct timezone *) NULL);  
  
      unsigned long space = (end.tv_sec - start. tv_sec) * 1000000 + end.tv_usec  
              - start. tv_usec;  
  
     sleepCost += space;  
  
      if (0 == times%1000) printf ("\n-----sleep cost-------\n%lu usec\n", space);  
  
      int sec, usec;  
  
      struct timeval tv;  
  
      gettimeofday(( struct timeval *) &tv, ( struct timezone *) NULL);  
  
     sec = ( int) tv. tv_sec;  
  
     usec = ( int ) (tv.tv_usec % 0x100000);  
  
      sprintf(uid, "%08x%05x" , sec, usec);  
  
      return 1;  
}  
  
int main( int argc, char * argv[]) {  
  
      struct timeval start, end;  
  
      gettimeofday(( struct timeval *) &start, ( struct timezone *) NULL);  
  
      for ( int i = 1; i <= 10000; i++) {  
  
           char data[20];  
  
          getUniqid(data,i);  
  
     }  
      gettimeofday(( struct timeval *) &end, ( struct timezone *) NULL);  
  
      unsigned long space = (end.tv_sec - start. tv_sec) * 1000000 + end.tv_usec  
              - start. tv_usec;  
  
      printf( "\n-----cost-------\n% lu usec\n  \n-----sum sleep sost-------\n% lu usec\n" , space,sleepCost);  
  
}

🎜
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn