您好,欢迎访问一九零五行业门户网

PHP uniqid函数执行缓慢的问题

前段时间某个需求:客户提交简单的表单可以创建一个适应于全终端(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 '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; }
执行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.982442m,通过打印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 ("\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); }
其它类似信息

推荐信息