PHP uniqid関数の実行が遅いという問題

8009 ワード

この間、ある需要:お客様が簡単なフォームを提出すると、全端末(PC、Pad、Phone)に適したブレード活動H 5ページを作成することができ、お客様がオンラインで限度額6 Wの賞品コードを生成できる機能に関連しています.
各アクティビティ賞品コードの一意性を保つ必要があるため、まずPHPのuniqid関数を用いてUUID(Universally Unique IDentifier、GUIDとも呼ばれ、グローバル一意識別子であり、アルゴリズムによって生成される一意の識別子)を生成する準備をして生成する.
しかし、生成1 Wテストを使用すると、生成に数十秒かかり、データベースに挿入する時間も含まれていないことがわかり、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秒近くの時間生成が必要で、1回の実行で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 'more entropy' 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;
}

1 W回で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;

最終的に1 W賞品コードを生成するには39.99587739が必要であることが分かった.995877秒、usleep間隔時間の合計39.9842 m、usleep時間を印刷することで、usleep(1)がプロセスから起動するまで4000マイクロ秒かかることがわかり、もともとusleepが精度に達していないことを知っていたが、あまりにも差があった.
最後に以下のコードで賞品コードを生成しました
   
/**
     *                 setNo
     * $pageId   ID
     * $level     
     * $numbers        
     */
    public static  function generateCDKEYAndSave($pageId,$level,$numbers){

        $level1Prefix =array(2,5,9,'E','F','M','N','Q','K','Z');//      

        $level2Prefix =array(1,3,7,'A','C','J','R','U','V','X');//      

        $level3Prefix = array(4,6,8,'B','D','G','H','I','L','O','P','R','S','T','W','Y');//      

        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.'A#1$v&'.$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 ("
-----sleep cost-------
%lu usec
", 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( "
-----cost-------
% lu usec

-----sum sleep sost-------
% lu usec
" , space,sleepCost); }