phpのチューニングによるサイト表示速度の改善
先日、ふと、何気なくトップページの表示に随分時間がかかることに気づきました。
画像が多いのでもともと重いほうだとは思うのですが。
普通だと私の環境で4秒ほどで表示されるのが、ひどいときには60秒以上もかかる。
それもその重い時の度合いが多くなってきているよう。
そしてその日の午前中はずっとその状態。
これは気にするなと言われても気にせずにはいられません。
サーバーはロリポップを使っています。
とても安くてありがたいのです。
ネット上には安かろう悪かろうで、遅いとか重いとか書いてあるところもちょくちょく目につきますが、最近になってこの重い状況になるまでは、遅いとか重いとかいった感じは一切ありませんでした。
他にさくらも使用していますが、FTPによるファイルの転送を含めてむしろロリポップの方が速度的にだんぜん優れていると感じていたぐらいでした。さくらの場合はなぜかphpMyAdminに入れないことがちょくちょくあって、ここぞという時に使えないというのはとても印象を悪くするものです。私の実際に使用している感じでは、ロリポップの方が親しみがあります。
それはともかく、ちょうど重くなってきたころとWAF(Web Application Firewall)が使えるようになったころがだいたい同じだったように思って、それが原因かとも思いましたが。
とりあえずサポートにメールを入れてみましたが予想通り。
その時間帯には特にサーバーは混雑していた様子ではなく正常とのこと。
使っているスクリプトやプラグインが原因となることもあるとのことでした。
でも、不思議なことにそれ以降、60秒を超えて待たされるということは無くなったように思います。
とはいっても、これはなんとかしなくてはいけません。
今一度、基本からphpのスクリプトを見直して見る事にしてネット探索。
色々あるなかで良く使っていて適応できそうなものをもらってくると。
- 文字列はダブルクォートよりもシングルクォートの方がはやい。
- ループはforよりもwhileの方がはやい。
- str_replaceよりもstrtrの方が高速。
- インクリメント++は後$i++よりも前++$iの方が速い。
ぐらいですかね。
あと、explodeよりもpreg_splitの方が速いと書いてあるサイトもあったが、これはちょっと疑問。
正規表現のエンジンを使うpreg_splitよりもexplodeの方が高速だとマニュアル本に書いてあった記憶がある。
自分で書いたfunction.phpを始めからじっくりと見て行くと、やたらとマルチバイト対応のmb_のついた関数を使っている。
phpを学び始めた頃は、いらぬところでつまづきたくないという気持ちがあって、なにかとmb_付を使っているのだと思います。
でも、ちょっと考えるとこれはマルチバイト対応でない方がだんぜん速そう。
ここのサイトは鳥画像サイトで、鳥種によっては専用の背景画像を作って、鳥種の分類に使っている投稿タグによってphpからjavascriptを設定してページ読み込みの時に背景を変えるということをやっています。
その判断に20数個のelseifが連なり、それ以上の数のmb_strposが並んでいます。
この部分も見るからにボトルネックとなっていそうな感じ。
で、ここを通るデータは投稿タグのスラッグなのでマルチバイト対応である必要は全く無し。
恥ずかしながらその部分の関数を載せてしまうと、
<?php
function rstagid($tags){
$chgtagid=0;
if(mb_strpos($tags,'sankou')!==false){
$chgtagid=1;
}elseif(mb_strpos($tags,'kibitaki')!==false){
$chgtagid=2;
}elseif(mb_strpos($tags,'bitaki')!==false){
$chgtagid=7;
}elseif(mb_strpos($tags,'komimizuku')!==false){
$chgtagid=4;
}elseif(mb_strpos($tags,'erimaki')!==false){
$chgtagid=5;
}elseif(mb_strpos($tags,'osidori')!==false){
$chgtagid=6;
}elseif(mb_strpos($tags,'itadaki')!==false){
$chgtagid=8;
}elseif(mb_strpos($tags,'daisyaku')!==false or mb_strpos($tags,'houroku')!==false){
$chgtagid=9;
}elseif(mb_strpos($tags,'yatsugasira')!==false){
$chgtagid=10;
}elseif(mb_strpos($tags,'toratsugumi')!==false){
$chgtagid=11;
}elseif(mb_strpos($tags,'renjaku')!==false){
$chgtagid=12;
}elseif(mb_strpos($tags,'yamadori')!==false){
$chgtagid=13;
}elseif(mb_strpos($tags,'semi')!==false){
$chgtagid=14;
}elseif(mb_strpos($tags,'oosorihasi')!==false or mb_strpos($tags,'ogurosigi')!==false){
$chgtagid=15;
}elseif(mb_strpos($tags,'tageri')!==false){
$chgtagid=16;
}elseif(mb_strpos($tags,'masiko')!==false or mb_strpos($tags,'uso')!==false or mb_strpos($tags,'sime')!==false or mb_strpos($tags,'hiwa')!==false){
$chgtagid=17;
}elseif(mb_strpos($tags,'fukurou')!==false or mb_strpos($tags,'zuku')!==false){
$chgtagid=18;
}elseif(mb_strpos($tags,'shorebird')!==false){
$chgtagid=3;
}elseif(mb_strpos($tags,'enaga')!==false){
$chgtagid=19;
}elseif(mb_strpos($tags,'ajisas')!==false){
$chgtagid=22;
}elseif(mb_strpos($tags,'taka')!==false or mb_strpos($tags,'nosuri')!==false or mb_strpos($tags,'tyuuhi')!==false or mb_strpos($tags,'tsumi')!==false){
$chgtagid=23;
}
return $chgtagid;
}
?>
こんなのです。
なるべく出現頻度の大きいものを始めの方に連ねるようにしてはいるのですが。
まずはこのmb_strposを変更することからですが、実際に違いがあるのかどうかということを見て見たくなりました。
で、この関数をそのまま使って以下のようなベンチを作って試してみる事に。
<?php
//適当に投稿タグにある鳥名で配列を作って。
$target=array('sankou','kibitaki','oosori','ootaka','tamasigi','nobitaki','shorebird','nosuri','hiyodori','kogara','niwatori','yamadori','tyogenbo');
$i=0;
$gettime = array();
$result=array(0,0,0);
//目的の関数を通さない空回しの時の結果を得る
echo '<p>for ループのみ10,000回空回し</p>';
for($j=1;$j<4;$j++){//それぞれ3回結果を得る
$k=0;
$gettime[0]=microtime(true);
while($k<10000){
if($i>12){
$i=0;
}
$tags=$target[$i];
$i++;
$k++;
}
$gettime[1]=microtime(true);
$ans=$gettime[1]-$gettime[0];
$result[0]=$result[0]+$ans;
echo '<p>'.$j.'回目 : '.sprintf('%0.5f',$ans).'秒</p>';
}
$result[0]=$result[0]/3;
echo '<br><p>mb_付</p>';
for($j=1;$j<4;$j++){
$k=0;
$gettime[0]=microtime(true);
while($k<10000){
if($i>12){
$i=0;
}
//鳥名を変えて関数を呼び出し
$tags=$target[$i];
$chgtagid=rstagidmb($tags);
$i++;
$k++;
}
$gettime[1]=microtime(true);
$ans=$gettime[1]-$gettime[0];
$result[1]=$result[1]+$ans;
echo '<p>'.$j.'回目 : '.sprintf('%0.5f',$ans).'秒</p>';
}
$result[1]=$result[1]/3;
$i=0;
echo '<br><p>mb_無</p>';
for($j=1;$j<4;$j++){
$k=0;
$gettime[0]=microtime(true);
while($k<10000){
if($i>12){
$i=0;
}
$tags=$target[$i];
$chgtagid=rstagidno($tags);//mb_無のstrposに変えた関数
$i++;
$k++;
}
$gettime[1]=microtime(true);
$ans=$gettime[1]-$gettime[0];
$result[2]=$result[2]+$ans;
echo '<p>'.$j.'回目 : '.sprintf('%0.5f',$ans).'秒</p>';
}
$result[2]=$result[2]/3;
$ans=($result[1]-$result[2]-$result[0])/10000;
echo '<br><p>1回分の平均差 :'.sprintf('%0.8f',$ans).'秒</p>';
?>
とりあえず違いが見てわかればいいかなと。
ついでに、strposの代わりに使えるstrstrについてもやってみました。言うまでもなく呼び出す関数のstrposをstrstrに代えただけです。
で、結果はというと。
- for ループのみ10,000回空回し
- 1回目 : 0.00115秒
- 2回目 : 0.00112秒
- 3回目 : 0.00115秒
- mb_strpos
- 1回目 : 0.32173秒
- 2回目 : 0.29178秒
- 3回目 : 0.28995秒
- strpos(mb_無)
- 1回目 : 0.08173秒
- 2回目 : 0.08196秒
- 3回目 : 0.08326秒
- 1回分の平均差 : 0.00002177秒
- strstr
- 1回目 : 0.08297秒
- 2回目 : 0.08471秒
- 3回目 : 0.08545秒
- 1回分の平均差 : 0.00002156秒
ちなみにexplodeとpreg_splitの違いも上のベンチをそのまま使ってやってみました。
ハイフンでつなげた文字列を作って、
<?php
$targetb='sankou-kibitaki-oosori-ootaka-tamasigi-nobitaki-shorebird-nosuri-hiyodori-kogara-niwatori';
?>
それをそれぞれexplodeとpreg_splitで分割して配列にするだけのもの。
上のベンチの関数の部分をそれぞれ下の文に入れ替えただけ。
<?php
$chgtagid=explode('-',$targetb);
$chgtagid=preg_split('/-/',$targetb);
?>
これの結果はやはりexplodeの方が速い。
- explode
- 1回目 : 0.02202秒
- 2回目 : 0.02201秒
- 3回目 : 0.02188秒
- preg_split
- 1回目 : 0.03462秒
- 2回目 : 0.03463秒
- 3回目 : 0.03483秒
- 1回分の平均差 :0.00000108秒
ついでに同じようにwhileとfor、インクリメントの前後による違いについてもやってみました。
ほとんど同じ以下のベンチ。
インクリメントの前後による違いはwhileのループを2つにしてインクリメントの部分を入れ替えただけ。
<?php
$gettime = array();
$result=array(0,0,0);
echo '<p>for ループのみ10,000回空回し</p>';
for($j=1;$j<4;$j++){
$gettime[0]=microtime(true);
for($k=0;$k<10000;$k++){
}
$gettime[1]=microtime(true);
$ans=$gettime[1]-$gettime[0];
$result[0]=$result[0]+$ans;
echo '<p>'.$j.'回目 : '.sprintf('%0.5f',$ans).'秒</p>';
}
$result[0]=$result[0]/3;
echo '<br><p>while ループのみ10,000回空回し</p>';
for($j=1;$j<4;$j++){
$k=0;
$gettime[0]=microtime(true);
while($k<10000){
++$k;
}
$gettime[1]=microtime(true);
$ans=$gettime[1]-$gettime[0];
$result[1]=$result[1]+$ans;
echo '<p>'.$j.'回目 : '.sprintf('%0.5f',$ans).'秒</p>';
}
$result[1]=$result[1]/3;
$ans=($result[0]-$result[1])/10000;
echo '<br><p>1回分の平均差 :'.sprintf('%0.8f',$ans).'秒</p>';
?>
そして結果はというと、
- for ループのみ10,000回空回し
- 1回目 : 0.00152秒
- 2回目 : 0.00144秒
- 3回目 : 0.00142秒
- while ループのみ10,000回空回し
- 1回目 : 0.00080秒
- 2回目 : 0.00080秒
- 3回目 : 0.00096秒
- 1回分の平均差 :0.00000006秒
- whileループ、++前 10,000回空回し
- 1回目 : 0.00080秒
- 2回目 : 0.00082秒
- 3回目 : 0.00083秒
- whileループ、後++ 10,000回空回し
- 1回目 : 0.00091秒
- 2回目 : 0.00092秒
- 3回目 : 0.00090秒
- 1回分の平均差 :0.00000001秒
いづれにしてもそれぞれの違いはとてもとてもわずかな差でしかありませんが、チリも積もればというところでしょう。
実際にコードを見ていくにあたって留意していったのは次の事柄。
- 文字列に使っているダブルクォートをシングルクォートに。
- ダブルクォートを使っている場合は包含している変数を展開するという機能が働くわけで、そういう機能がある分、無いものよりも余計な処理が含まれているはずです。
- 必要の無いところでのマルチバイト対応関数の使用を止める。
- 実験して結果が出ているとおり。実はこれが沢山あるので効果があるとするとこれによるところが大きいのでは。
- str_replaceをstrtrに変更。
- 試してはいませんが、ネット上にあるのでやってみます。
ただし、strtrの場合は変換後の文字列に空文字を指定して文字列を取り去る使い方は出来ないようです。
変換前と後に対応する文字が無いと無視されてしまうという事です。 - なるべくオブジェクトや配列のソートの回数を減らす。
- wordpressのtaxonomy等の関数を使う時はソートが必要であればその時に指定して、いらぬソートをしないようにする。
あとはタグクラウドをはじめ、出力してる鳥種のリストが多いので、そのコードをダイエットして高速化できれば効果は大きいと思えます。
具体的にはタグページのリンクはurlのオプションに「/?tag=」+投稿タグのslugというパターンで決まっているので、関数を使わずにそのまま文字列で指定するとか、データをオブジェクトのまま持回らないで、必要なものだけ配列に入れて使うとかしてみました。
FirefoxとChromeはアドオンにてサイトの表示にかかる時間が表示されるようにしています。
全てのテンプレートファイルにおいて見直して手を入れましたが、表示される時間を見ていると明らかに効果はありました。
やはり複数のリストを表示する部分と、背景画像を入れ替える部分がボトルネックとなっていたようです。
私のような完全閉鎖的独学プログラマーは効率の良いコードの書き方というのが出来ないというか知らないというか。
今回のような問題があると、ただ動けばいいといったコードから一歩進んだより速くするにはということを考えなければならなくなります。
問題はうっとおしいけれど、これでまた少し進歩できた気がします。
Post : 2013/04/02 22:50
Comments feed
Trackback URL : https://strix.main.jp/wp-trackback.php?p=33737