Home  >  Article  >  Backend Development  >  The problem of slow execution of PHP uniqid function

The problem of slow execution of PHP uniqid function

巴扎黑
巴扎黑Original
2016-11-12 10:23:331161browse

A certain requirement some time ago: Customers submit a simple form to create an H5 page for scratch card activities that is suitable for all terminals (PC, Pad, Phone), which involves the function that customers can generate limit 6W prize codes online.

Because we need to keep each event’s prize code unique, we first prepare to use PHP’s uniqid function to generate UUID (Universally Unique IDentifier, also called GUID, a globally unique identifier, a unique identifier generated by an algorithm). generate.

But when we used the generated 1W test, we found that it took tens of seconds to generate some data, not including the time to insert into the database. Then we used xhprof to write a simple example for performance testing

Test results:

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

unexpectedly takes nearly 40 seconds to generate. A single execution takes 3969 microseconds, which is 0.003969 seconds to generate. If the user submits the form and generates a redemption code at the same time, it will take 4 minutes to respond to the user in the worst case. Of course, it can be generated asynchronously using the message queue, but why does uniqid take so much time to generate a simple string?
Then check the implementation source code of uniqid, the code is posted below

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

Looking at the logic, there are no complicated operations. It also simply processes the current time seconds and microseconds, and then writes a simple test

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

Execute 1W It only takes 2000 microseconds. Why is this? However, we found that there are a lot of duplications in the generated uid, so we paid attention to the usleep function in the original code.
Added the usleep function in the test, this time it took nearly 40 seconds to match the PHP result. usleep is here to keep every The uid generated this time is different.
The problem occurred in the usleep function, and then add the interval time before and after usleep. The code is as follows

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;

Finally, it was found that it takes 39.99587739.995877 seconds to generate the 1W prize code, and the total usleep interval time is 39.982442m. By printing the usleep time It was found that each time usleep(1) takes 4000 microseconds from the process suspension to waking up, I knew that usleep could not achieve the accuracy, and it was too far away.
Finally, the following code was used to generate the prize code

/** 
     * 生成兑换码并保存到数据库  返回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;  
  
    }


Comes with uuid test code

#include   
#include   
#include   
#include   
  
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);  
  
}


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn