`
OrangeHolic
  • 浏览: 260903 次
  • 来自: 北京
社区版块
存档分类
最新评论

PHP uniqid函数执行缓慢的问题

    博客分类:
  • PHP
阅读更多
前段时间某个需求:客户提交简单的表单可以创建一个适应于全终端(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);

}
分享到:
评论

相关推荐

    php 常用函数集合

    10. `eval()`:将字符串作为PHP代码执行,但需谨慎使用,因为这可能导致安全问题。 11. `defined()`、`define()` 和 `constant()`:与常量相关,`define()`用于定义常量,`defined()`检查常量是否存在,`constant()...

    常用PHP函数小全

    `eval()` 函数执行一个字符串参数中的 PHP 代码。虽然这个函数非常强大,但也可能带来安全风险,因此应谨慎使用。 #### die() `die()` 函数与 `exit()` 类似,都用于终止脚本执行。不同之处在于 `die()` 可以接受...

    整理非常全的PHP常用函数大全.pdf

    1. `usleep()`:此函数用于延迟代码执行,参数是以微秒为单位的时间,可用于实现短暂的等待。 2. `unpack()`:这个函数用于将二进制字符串解包成多个变量,根据提供的格式字符串解析数据。 3. `uniqid()`:生成一...

    PHP函数大全

    `eval()`函数执行字符串中的PHP代码。虽然功能强大,但由于安全性问题,应谨慎使用。 #### 16. die() `die()`函数与`exit()`类似,用于终止脚本执行。通常在错误检测后调用,以阻止进一步的执行。 #### 17. ...

    基于PHP的UrlWorker网址工匠(网址缩短程序)php版源码.zip

    PHP的`uniqid()`函数也可以用来生成基于时间戳的唯一ID,但可能需要配合哈希函数以降低冲突风险。 4. **URL路由**:短码需要能正确地解析并重定向到原始URL。在PHP中,这通常通过解析URL并根据短码执行查询来实现。...

    php性能文档资料

    2. **使用basename函数**:使用`basename()`函数来提取文件的基本名称,以避免路径穿越攻击。 3. **限制文件权限**:确保服务器上的敏感文件具有正确的权限设置,以防止未经授权的访问。 通过以上方法,可以有效...

    自编PHP多图片上传类

    3. **文件验证**:上传类需要包含验证功能,例如检查文件类型(例如只允许JPG、PNG等)、文件大小限制以防止过大文件导致服务器问题,以及检查文件是否为有效图片(如通过`exif_imagetype()`函数)。 4. **文件移动...

    php购物车代码(分析)

    在代码中,如果session和scid(购物车ID)都不存在,就会生成一个新的session ID,这个ID是通过`md5(uniqid(rand()))`函数产生的,确保其唯一性。同时,使用`setcookie()`函数设置一个名为scid的cookie,以便在用户...

    PHP并发场景的几种解决方案.docx

    如果在事务执行过程中`rest_count`的值被其他并发进程更改,整个事务将被回滚,从而避免了并发问题。 另一种解决方案是使用文件排他锁(阻塞模式)。在Linux系统中,可以通过`flock()`函数对文件加锁,确保同一时间...

    php批量上传图片,对每张图片进行描述后,存储数据库

    `uniqid()`函数可以生成一个唯一的字符串。 10. **优化与安全**:上传后的图片可能需要进行缩放、裁剪等操作以适应网站需求,可以使用GD库或ImageMagick进行图像处理。同时,确保上传目录的权限设置正确,避免直接...

    php实现博客,论坛图片防盗链的方法

    首先,PHP中的uniqid()函数用于生成一个基于当前微秒数的唯一字符串。它可以通过传入一个字符串作为前缀,来提高生成的唯一字符串的可辨识性。如果将uniqid()与session结合使用,可以在服务器端生成一个只对当前会话...

    PHP下载远程文件到本地存储的方法

    在函数中,使用uniqid生成一个唯一的文件名,如果下载成功,函数返回生成的文件名,否则返回false。 使用这些函数时,必须保证PHP环境配置允许远程文件的访问,且PHP代码的执行环境(如服务器配置)安全,避免潜在...

    8个必备的PHP功能实例代码

    生成唯一ID在PHP中最常用的方法是`uniqid()`函数,它基于微秒时间戳和随机数生成一个全局唯一的标识符。可以设置前缀,增加可读性,或者使用`true`作为第二个参数以提高唯一性。 7. **序列化**: PHP的`serialize...

    浅析is_writable的php实现

    在上述代码中,错误抑制操作符@被用于fopen()函数之前,其目的是防止脚本在执行fopen()时因文件不存在或权限问题等导致的错误信息输出。在生产环境的PHP代码中,通常不推荐使用@来抑制错误信息,因为它会隐藏错误...

    基于PHP的urlShort1.1.2短网址服务实现程序源码.zip

    3. **URL编码与解码**:在处理URL时,需要确保其符合URI标准,可能需要使用PHP的`urlencode()`和`urldecode()`函数进行编码和解码。 4. **自定义短码生成策略**:为了防止哈希冲突导致的短码重复,可以采用自增ID、...

    PHP 并发场景的几种解决方案

    在PHP中,通过`flock()`函数可以对文件进行锁定,确保同一时间只有一个进程可以访问特定资源。以下是一个使用文件锁定的示例: ```php $http = new swoole_http_server("0.0.0.0", 9510); $http-&gt;set([...]); $...

    PHP文件上传和下载

    可以使用`basename()`去除路径信息,`uniqid()`生成唯一ID,结合安全的文件命名策略。 3. **权限控制**:确保上传文件的目录只有读写权限,没有执行权限,防止脚本文件被误执行。 4. **文件大小限制**:设置合理的...

Global site tag (gtag.js) - Google Analytics