少し前の要件: 顧客は、すべての端末 (PC、パッド、電話) に適したスクラッチ カード アクティビティ用の H5 ページを作成するための簡単なフォームを送信します。これには、顧客が最大 60,000 個の賞品コードを生成できる機能が含まれます。オンライン。
各イベントの賞品コードを一意に保つ必要があるため、最初に 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 秒近くかかります。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; }
。 1W の実行には 2000 マイクロ秒しかかかりません。これはなぜですか?ただし、生成された uid に重複が多いことが判明したため、元のコードの usleep 関数に注目しました
テストに usleep 関数を追加しました。今回は、PHP の結果と一致するまでに 40 秒近くかかりました。 . usleep はすべてを保持するためにここにあります。 今回生成された uid は異なります。
問題が発生したのは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); }