PHP Tips!最速の先頭0埋めの固定長乱数値の生成方法!でも先頭1埋めの方がお勧め!sprintfを使わないだけで約10倍高速!?

PHP Tips!最速の先頭0埋めの固定長乱数値の生成方法!でも先頭1埋めの方がお勧め!sprintfを使わないだけで約10倍高速!?
PHP Tips!最速の先頭0埋めの固定長乱数値の生成方法!でも先頭1埋めの方がお勧め!sprintfを使わないだけで約10倍高速!?

 

 
読了目安:122

Are you want to translate this page to English? Please click this link to translate via ‘©Google Translate'!

ganohr.net favicon

みなさんこんにちは、ガノー(Twitter:Ganohr)です。

1年に1回くらいの割合で「先頭0埋めの固定長乱数値」を生成したいと思うことがあります。

 

こういった用途はDBなどの定義において、以前は「Auto Increment(AI)」で値を生成したが、

  • 複数のDBをマージする上でAIだと都合が悪くなった
  • 別システム上のDBとマージしないといけず、Unique Indexを効率的に生成する必要がある
  • 件数が多いが、実際はそこまで厳密に識別可能な一意性は必要ない※

といった条件で利用されるように思います。

※ 数千万分の一程度の確率で同値が生成されて挿入/更新に失敗しても、その値は無視して良いといった状況が想定されます

 

このときDBサーバーで処理するのではなく、PHPプログラムでバッチ処理を行う場合などに「先頭0埋めの固定長乱数値」という実装が必要になります。

ただ、こうした用途の場合は「先頭1埋めの固定長乱数値」の方が適切かもしれませんけどね(本記事で実装を理由含め解説しています)。

更新履歴
2023/02/02 情報を拡充
2023/01/20 公開

PHP Tips!:sprintfよりも速く、先頭0埋めの固定長乱数値を生成するコード

ひとまずコード例は以下の通り。

 

function ganohrs_get_random_number_string(int $len = 9):string {
    $max = pow(10, $len);
    // echo "max = $max\n";
    $rnd = mt_rand(0, $max - 1);
    // echo "rnd = $rnd\n";
    return substr($rnd + $max, 1);
}
echo ganohrs_get_random_number_string() . "\n";

※ PHP7.4にて実装。タイプヒンティングが無効なバージョンではintやstring等を除去してください。

サムネイルPHP: 型宣言 - Manual ... 外部サイトへアクセスwww.php.net

 

このコードはganohrs_get_random_number_string(int):stringという関数が定義されています。

ganohrs_get_random_number_string(4)

とやれば4桁の先頭0埋めの数値の文字列が返却されます。

 

この定義は先頭0埋めを加算とsubstrのみで実現しているため、理論上最速です。

ネットを検索すると「0埋め如きにsprintfなんて使う無駄なゴミコード」が溢れてるんで、一応記事にしました。

 

ただし、mt_randは精度が比較的悪いので暗号化の用途で使ってはダメです。

加えて、理論上最速ではあっても実測上最速ではないので、その意味でも次に示す先頭1埋めを推奨します。

 

また、PHPは、

32ビット環境ではintの範囲は「-21474836482147483647」なので、この関数では最大9桁までしか対応できません

64ビット環境ならintの範囲は「-92233720368547758089223372036854775807」なので、この関数では最大18桁までしか対応できません

サムネイルPHP: 定義済みの定数 - Manual ... 外部サイトへアクセスwww.php.net

 

念のため桁数は意識して使ってください。

そして「DB等へ格納する用途では、先頭1埋めを推奨」します。

PHP Tips!:最速で先頭1埋めの固定長乱数値を生成するコード(先頭0埋めよりこっちを推奨)

function ganohrs_get_random_number_string(int $len = 9):string {
    $min = pow(10, $len - 1);
    $max = pow(10, $len);
    // echo "max = $max\n";
    $rnd = mt_rand($min, $max - 1);
    // echo "rnd = $rnd\n";
    return $rnd;
}

 

こちらもsprintfを使わないので最速です。

先頭0埋めと1埋めはどちらも計算だけで実現できますね。

 

ちなみに先頭9埋めは計算ではできませんが、こちらもsprintfを使わずに実装できます。

function ganohrs_get_random_number_string(int $len = 9):string {
    $max = pow(10, $len);
    // echo "max = $max\n";
    $rnd = mt_rand(0, $max - 1);
    // echo "rnd = $rnd\n";
    return str_pad($rnd, $len, '9', STR_PAD_LEFT);
}
echo ganohrs_get_random_number_string() . "\n";

 

こちらはstr_padを使う必要はありますが、ほとんど速度的な負荷がありません。

なぜ先頭0埋めよりも1埋めの方が良いのか?

DBへ値を入れた際に、数値型の場合先頭0は無視されるため、固定長になりません

 

あくまでも仕様上の注意点というか、結合試験等で案外ミスに繋がりやすいのが先頭0埋めの仕様です。

DBのテーブルへ固定長の整数値型へ値をインサートしたりアップデートした場合に、先頭の0を欠落させるか否かはエンジンごとにまちまちです(基本的に欠落します)。

 

場合によってはせっかく固定長にして入れ込んでも、結局DBから取得した値からは先頭の0が欠落しているため、いちいちプログラム側でそれを補填するという無意味な作業が発生することがあります。

そして、先頭を0で埋めるか1で埋めるかの違いは些末な問題であり、それによってデータの精度が落ちるといったことはありません。

したがって、「先頭0埋めは不具合を発生させうるが先頭1埋めは不具合を発生させないため、1埋めを推奨する」というわけです。

速度検証

最後にどのくらい速度差があるのか確認しておきましょう。

検証コードは以下の通りです。各関数を100万回実行して検証しました。

$time_s = microtime(true);
for($i = 0; $i < 1000000; $i++) {
    ganohrs_get_random_number_string();
}
$time_e = microtime(true);
$time_p = $time_e - $time_s;

echo "start $time_s\n"; echo "end   $time_e\n"; echo "past  $time_p\n";

 

先頭0埋めの固定長乱数値

出力例
start 1674159471.9139
end 1674159472.047
past 0.13305521011353

 

先頭1埋めの固定長乱数値

出力例
start 1675277675.3244
end 1675277675.4586
past 0.13426899909973

 

先頭9埋めの固定長乱数値

出力例
start 1674159509.8315
end 1674159509.9626
past 0.13107109069824

 

結果は9埋め版の方が早いですね。

 

また、よくあるsprintfによる実装も計測しましょう。

function ganohrs_get_random_number_string(int $len = 9):string {
    $max = pow(10, $len);
    // echo "max = $max\n";
    $rnd = mt_rand(0, $max - 1);
    // echo "rnd = $rnd\n";
    return sprintf('%0' . $len . 'd', $rnd);
}
echo ganohrs_get_random_number_string() . "\n";

sprintf版先頭0埋めの固定長乱数値

出力例
start 1674158811.7433
end 1674158812.9987
past 1.255362033844

 

…遅すぎて絶句。

約9.435倍も遅いってことが判明しました。

こんなのを堂々と公開しているネット記事ばかりなんでうんざりです。

弱い型付けと厳密な型付けによる速度検証

PHP7以降ではdeclare構文にて厳密な型付けをファイル単位で有効化できます。

 

弱い型付けというのは、PHPの特徴の一つであり、文字列型で表現された値や数値型や浮動小数点数などの型を自動的に相互変換してくれる機能です。

JavaやC#などのオブジェクト指向プログラミングに慣れている方なら「オートボクシング」と言ったほうがわかりやすいかもしれません。

サムネイルPHP: 型宣言 - Manual ... 外部サイトへアクセスwww.php.net

 

この弱い型付けはいうなれば毎回自動的に型変換が必要か確認するため、速度的に多少不利になります。

とはいえほぼ誤差レベルの差しか出ませんが、誤差を除いていくとやはり若干厳密な型付けを利用したほうが早くなります。

function ganohrs_get_random_number_string(int $len = 9, string $pad = '1'):string {
    $max = pow(10, $len);
    // echo "max = $max\n";
    $rnd = mt_rand(0, $max - 1);
    // echo "rnd = $rnd\n";
    return str_pad($rnd, $len, $pad, STR_PAD_LEFT);
}
echo ganohrs_get_random_number_string() . "\n";

弱い型付けによる速度検証

出力例
start 1674159796.0418
end 1674159796.174
past 0.13219690322876

 

厳密な型付けによる速度検証

出力例
start 1675315804.3144
end 1675315804.439
past 0.1246018409729

※ 厳密な型付けのコードは次の結論を参照。

結論:phpでsprintfに替わり、先頭0/1/9埋めの乱数値を生成する最速コード

declare(strict_types=1);

function ganohrs_get_random_number_string(int $len = 9, string $pad = '1'):string {     $max = pow(10, $len);     // echo "max = $max\n";     $rnd = mt_rand(0, $max - 1);     // echo "rnd = $rnd\n";     return str_pad((string)$rnd, $len, $pad, STR_PAD_LEFT); } echo ganohrs_get_random_number_string() . "\n";

これは、省略可能な第二引数に先頭パディング文字として’0’や’1’や’9’を指定可能で、省略時は本記事おすすめの’1’が設定されるコードです。

 

速度はなとsprintfの10倍以上も高速です。

ということでDB等で利用することを想定している場合はこちらのコードをご利用ください

決してネットの有象無象のsprintfを恥ずかしげもなく使っているサンプルコードに騙されないように!

WordPressの不具合対応/カスタマイズ¥15,000~

PC歴25年超、SE歴10年超、WordPress運営歴7年超、WordPressエンジニア歴5年超のスキルとノウハウを提供します

当サイト管理人の「ガノー」(Ganohr)は、日本最大手且つ東証一部上場企業が運営するクラウドソーシングサイト『Lancers』にて、認定ランサーとして活動しています。


※ 認定ランサーとはLancersにより様々な能力 ( 高い仕事遂行率・高い顧客満足度・多くの実績、など ) を評価したプロフェッショナルを認定する制度です。

 

PHPカテゴリの最新記事