乱数発生器(パスワード生成サービス)がバージョンアップで高速化!
2018/01/07
乱数発生器(パスワード生成サービス)が Ver.2になって新登場!
乱数発生器(パスワード生成サービス) Ver.2
乱数発生器(パスワード生成サービス) Ver.2
https://s-giken.info/random/random.php
乱数発生器(パスワード生成サービス)ですが、内部処理を見直して、高速化したバージョン 2に生まれ変わりました。
これまでのツールは、30桁 10,000件でも「Fatal error: Maximum execution time of 30 seconds exceeded(最大 30秒の処理時間をオーバーしました)」というエラーが発生していました。
自分自身では、メールアドレスのパスワードを 10件程度生成する際などにしか使っていませんでしたので気づいていませんでしたが、数万件単位の乱数を生成しようとしたら、エラーで止まって使えない状態でした。
もともとこの乱数発生器を作ったのは、とあるキャンペーンで数十万件の乱数が必要になったからでした。
ペットボトルなんかにシールが付いていて、シールをはがして、それについている英数字を入力してキャンペーンにエントリーする、みたいなものの元になる乱数を生成する必要があったからでした。
当時のエクセルは 6万行ほどしかなくエクセルで生成することができなかったため、PHPでプログラムを生成して...というのがきっかけでした。
なので、そういう用途を見越して作成して、公開していたにもかかわらず、そういうキャンペーン用途では使えない状態だったことに気づいて、今回リニューアルをすることにしたのでした。
乱数発生器(パスワード生成)の高速化のポイント
今回の高速化にあたっては、2つの処理を見直しました。
1つ目が、乱数を発生させる関数を変更したことです。
2つ目が、重複チェックの処理方法を変更したことです。
乱数発生関数を変更して高速化する
1 2 3 4 5 6 7 8 9 10 |
$str_array = array( "a", "b", "c", "d", "e" ); // Ver.1での処理 $rand_array = array_rand ( $str_array ); echo $str_array[$rand_array] . "<br>"; // Ver.2での処理 $str_count = count ( $str_array ); $rand_mt = mt_rand ( 0, $str_count -1 ); echo $str_array[$rand_mt] . "<br>"; |
かつての乱数を発生させる処理としては、「array_rand()関数」を使って、配列の中にある文字を取得する処理で文字を取得していました。
ですが、「array_rand()関数」では処理が遅いので、より高速な「mt_rand()関数」を利用することにしました。
「array_rand()関数」は内部で「rand()関数」を使用していますが、下記の PHPのマニュアルには「rand()関数」より「mt_rand()関数」の方が 4倍以上高速と書いてあります。
http://php.net/manual/ja/function.mt-rand.php
実際の処理では、下記の様に、入力された桁数($ketasuu)の数だけ for文で繰り返し実行し、所定の桁数の文字列を生成しています。
1 2 3 4 5 6 7 |
$str_array = array( "a", "b", "c", "d", "e" ); $str_count = count ( $str_array ); for ( $i = 1, $i <= $ketasuu; $i++ ) { $rand_mt = mt_rand ( 0, $str_count -1 ); $str .= $str_array[$rand_mt]; } |
さらには、この文字列を生成する処理を、求められる件数分実行する、ということですね。
重複チェックの処理方法を変更して高速化
乱数を生成する条件として、「重複を認める」というチェックボックスがあります。
これにチェックをを入れない場合は、生成した乱数の中に同じ乱数がないかどうかをチェックしながら乱数を生成する仕様になります。
これは、キャンペーンなどに使う乱数は、重複している乱数が入っているとトラブルの原因になりますので、重複を省く機能を実装していました。
ですが、この重複チェックをする機能が非常に重く、生成するレコードの件数が増えていくと、指数関数的に処理が重たくなる処理になっていました。
もともとの処理は以下のような感じです。
乱数を生成し、その生成した乱数が、生成した乱数の配列の中にあるかどうかを「in_array()関数」を使ってチェックし、重複していた場合は、カウントを戻してもう一度乱数を取得しなおす、という処理になっています。
1 2 3 4 5 6 7 8 9 |
for ( 必要な件数 ) { $str = 乱数生成; if ( in_array ( $str, $get_array ) ) { カウントを一つ戻す; } else { $get_array[] = $str; } } |
ですが、乱数を一つ生成するごとに「in_array()関数」で重複チェックをしますので、件数が増えると徐々に重たくなっていくという一番ネックになっている処理でした。
そのため、あれこれ考えた結果、「array_unique()関数」を使って、全件生成した後に重複チェックをしよう、という決断を下しました。
1 2 3 4 5 |
for ( 必要な件数 ) { $str = 乱数生成; $get_array[] = $str; } array_unique ( $get_array ); |
これにより、劇的に高速化でき、40万件でも 10秒程度で生成できるようになりました。
実際の問題としても、桁数が多ければ多いほどほぼ重複はしませんので、最後に重複チェックをすれば OKという判断をしました。
重複チェックは最後でいいと判断した理由
乱数を生成した後に、「array_unique()関数」で重複チェックを実施すると、重複していた乱数は削除されるため、生成した件数がやや減ります。
具体的には、例えば、100件生成しても 2件の重複があれば 98件しか生成されません。
場合によっては、90件くらいしか生成されない場合もあるかもしれません。
それをどうしようかと考えたわけですが、100件必要な場合でも 100件ピッタリに生成する必要がある場合は皆無であろうと判断したわけです。
つまりは、100件必要なら、110件の生成で実行すればいい。
重複があって 105件くらいに減って出力されても、100件は超えているわけですので、あとはアナログ的に 5件削って 100件として使ってもらえばいいじゃないか、と。
処理が重くて必要な 100件が作れないより、問題はない、と判断したわけです。
それに、桁数が増えると、そもそもほぼ重複はしなくなるからです。
例えば、4桁の数字のみの場合。
生成される文字列は「0~9999」でありまして、10の 4乗の 1万通りあります。
この内、100件を乱数で生成した場合、重複する確率は 1万分の 100、1%になります。
つまりは、100件で生成すれば 99件くらいは生成されるわけです。
これが英数字を使った文字列の 4桁乱数の場合、
生成される文字列は、小文字 26文字、大文字 26文字、数字 10文字の合計 62文字の組み合わせになりますので、62の 4乗、14,776,336通りになります。
そして、この中から 100件を乱数で生成する場合、重複する確率は 14,776,336分の 100ですので、0.000677%となります。
これは、100件の生成を 1,000,000回実施すると 7回くらい 99件の場合があるかも、というくらいの確率です。
もしくは、4桁の乱数を 1,000,000件生成する場合には、7%くらい確率で 99件の場合があるかもしれない、という確率です。
なので、運が悪くなければ重複しない、と判断したわけです。
ちなみに、「乱数発生器(パスワード生成サービス) Ver.2」で生成できる最大の 30桁の乱数を生成する場合、生成できる文字列の組み合わせは、「5.9122213e+53」通りの乱数が生成できるわけです。
なので、桁数が増えれば増えるほど、重複しなくなるので、重複チェックは最後に確認程度に実施すればよろしかろう、という判断をいたしました。
それにより、劇的なスピード改善となり、数十万件のキャンペーン用の乱数コードを生成することも問題ない状態となりました。
乱数発生器(パスワード生成サービス) Ver.2 のまとめ
乱数発生器(パスワード生成サービス)ですが、最初に作ったのは 10年位前です。
その後、「エス技研ラボ」で提供する Webサービスとして作り直して、公開したわけですが、それから 4、5年経って高速化バージョンへのリニューアルとなりました。
前作は、「array_rand()関数」という便利な関数を知ったので、それを使いたい!と思って作ったこともありまして、スピードを気にせず構築していました。
ですが、件数が増えると全然使えないシステムだったことにようやく気付きまして、今回のリニューアルとなったわけですが、これで、本来の目的にも使ってもらえるんじゃないかと思っています。
ぜひとも、多くの方に使っていただければ、と思っています!
ちなみに、前作を公開したときの記事が下記にありまして、今回書いたところ以外の解説もありますので、あわせてご覧ください。
PHP range関数を使って階乗と重複組み合わせを計算
また、JavaScriptを利用して、入力エリアの横に「パスワードを生成する」ボタンを追加する機能について下記に記事を書いています。あわせてご覧ください。
JavaScriptで「パスワードの生成」ボタンを追加する処理サンプル
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
ECCUBEの問い合わせフォームに任意の値を引数として渡す方法
ECCUBEのお問い合わせフォームに値を固有の情報を送りそれに基づいて処理をする方法を解説。ボタンの設置、受け取り側のテンプレート、プログラムのサンプルソースを提供。
-
配列の値をテキスト表示する際に「、」でつなげるときの処理方法の一例
配列の値を「、」でつないで出力する際、単純にforeachで繰り返し処理をすると「イヌ、サル、キジ、」となるが文字列最後の「、」を出力しない方法を3つ解説している。
-
PHPで配列の値をダブルクオーテーションで囲んでimplodeでカンマ区切りにする方法
PHPで配列の値を、preg_replace関数でクォーテーションで囲み、implode関数で「,(カンマ)」で区切ってテキスト化する方法。この方法であれば配列が空でも分岐の処理は必要なし!
-
ECCUBEのポイント設定、ポイント付与率を一括で変更する方法解説
ECCUBEの商品個別に設定してあるポイントを一括で変更する方法を解説。ECCUBEには商品個別のポイントを一括して変更する機能がありません。SQLを作成して一括置換!
-
PHPでスクレイピング。phpQueryとphp-simple-html-dom-parserの比較と設置方法
「PHP スクレイピング」で検索すると「phpQuery」ばかりヒットするが、10年以上も放置されている。なので今も開発が続いている「PHP Simple HTML DOM Parser」をオススメする。
-
XML形式の値を配列形式に変換・PHPでは simplexml_load_string()
XMLとは「Extensible Markup Language」の略でテキストベースのデータフォーマット。XMLをPHPで配列に変換するWebツールの紹介とその処理「simplexml_load_string()」関数についての解説。
-
GMOペイメントゲートウェイのjava.io.IOExceptionのエラー
ECCUBEの決済でGMOペイメントゲートウェイのモジュールを使ってテスト決済を行った場合の不具合、java.io.IOExceptionと言うエラーの原因と対策方法の解説です。
-
include、requireのパス指定をdirname(__FILE__)、__DIR__と書く理由
include、requireのパスの指定を dirname(__FILE__)、__DIR__で記述する理由に付いて解説。相対パス、絶対パスを直書き、パスを書かない場合は何が問題かを説明。
-
Smartyのテンプレート内の処理で計算、加工をする方法
Smartyのテンプレート上で変数を計算する、加工する方法を解説します。
-
PHPのデバッグで使う print_r、var_dump、var_exportの動作の違い
PHPのデバッグ等で変数や配列の中身を確認するために使用する関数print_r、var_dump、var_exportの動作の違い、仕様の違いについて確認した。var_exportがオススメ。