こんにちは。たきもとです。
今回はPHPでオブジェクト指向を使った時のメリットとデメリットを
-私のサイトをスクレイピングして
-動作速度の観点から
考えます。
結論は、
-よく考えてコーディングしないと処理が遅くなるよ?
ということです。
私はメインファイルをすっきりさせるためにclassを利用しているのですが、なんでもかんでもclassに詰め込んで処理速度が犠牲になる事がしばしばあります。
どこまで処理速度を落としていいのか?
はその時々で異なりますが・・。
そんな訳で、私はコードの
-保守性/管理性
-処理速度
この2つを天秤に掛けながらコーディングをしています。
今回の処理速度低下の本質は以前書いた記事と同じで
-functionの多重呼び出し
です。
その時の記事はこちらです。
PHPの処理が遅い原因を調査したら function 多重呼び出しが悪さをしていた件 | たきもとけんご.com
オブジェクト指向のメリット・デメリット
オブジェクト指向自体は本やネットで沢山解説されています。
私のオススメは次の2サイトです。
PHP入門 | ドットインストール
私が考えるメリット・デメリットは次の通りです。
メリット: ファイルの管理・変更がしやすい
まずはこれです。
私はプログラミングを始めてまだ半年程度です。
その中で困ったことは
-メインファイルのコードが汚いこと
でした。
じゃあどうすればメインファイルのコードがキレイになるのか。
そこで行き着いたのが
-外部ファイル化
-オブジェクト指向
です。
functionやdefineを外部ファイル化することでメインファイルがすっきりします。
また、classによって特定の機能をまとめればもっとファイルがすっきりします。
ここの処理を変えたいな・・?
と思ったら分けたファイルを修正すれば良いので。
管理がし易いです。
ここでこんな処理をさせたいな・・?
と思ったら、
$konnaDosa = $obj->doSomething();
のようにメソッドを呼び出せば済みます。
こんな感じで、オブジェクト指向を学ぶとメインファイルの
-どこで
-どんな処理をしているのか
が簡単に分かるようになるメリットがあります。
もしメインファイルに全ての記述を収めたら
-どこに
-何が
書いてあるのかを探さなければなりません。
世の中的にはこれを
-保守性
-管理性
が高い
と表現しています。
これが私が考えるオブジェクト指向のメリットです。
デメリット: 書き方に気をつけないと処理が遅くなる
次にデメリットです。
結論から言うと、
-書き方に注意しないと処理が遅くなる
点です。
具体例は後述するので、ここでは処理速度を計測するための準備について紹介します。
処理速度を確認する準備
以前書いた記事でも紹介した
-debug用の関数
を定義します。
以前デバッグ用の関数を書いた記事はこちら。
PHPの処理が遅い原因を調査したら function 多重呼び出しが悪さをしていた件 | たきもとけんご.com
今回は
-ブラウザに表示
-処理時間計測
のための関数を用意します。
デバッグ用関数
function.php
<?php /** * debug用の関数を用意 * * debug($title, $var) |配列内容をブラウザに表示 * debug_time |実行時間を表示 * */ function debug($txt_title, $var){ echo '<pre>'; echo '//--- debug ---//'."<br>"; echo '// '.$txt_title."<br>"; echo "$txt_title".'='."<br>"; var_dump($var); echo '</pre>'; } function debug_time($txt_title, $var_start){ echo '<pre>'; echo '//--- debug ---//'."<br>"; echo '// '.$txt_title."<br>"; $end_time=microtime(true); $cal_time = $end_time - $var_start; echo "開始時間: ".date('Y-m-d H:i:s',(int)$var_start)."<br>"; echo "終了時間: ".date('Y-m-d H:i:s',(int)$end_time)."<br>"; echo "処理時間:".sprintf('%0.1f',$cal_time).'[s]'."<br>"; echo '</pre>'; }
この2つの関数は
-処理の結果をブラウザで表示確認を行うため
-処理速度をブラウザで表示するため
に用意しました。
これらの動作については割愛します。
実行環境
今回の例では私のサイトのこのページをスクレイピングします。
//環境
Windows 8.1
64bit
xampp
Chrome
//スクレイピング対象サイト
私がオススメする配列の作り方 | たきもとけんご.com
//対象文字列
H2 タグ内テキスト
H3 タグ内テキスト
遅い例
まずは処理が遅い例です。
classSlow.php
<?php class ClassScrape{ //property private $pageNo; //const const TIMESLEEP = '3';//sleep時間 //constructor function __construct(){ $this->pageNo= '000000';//初期化 } public function setData($setPageNo){ $this->pageNo = $setPageNo; } public function getHtml(){ $urlBase = 'https://kengotakimoto.com/'; $url = $urlBase.$this->pageNo; sleep(self::TIMESLEEP);// 待ち時間 $html = file_get_contents($url); return $html; } public function getH2(){ $html = $this->getHtml(); $paramGetH2 = '@\<h2\>(.+?)\<\/h2\>@'; preg_match_all($paramGetH2, $html, $resultH2); return $resultH2[1]; } public function getH3(){ $html = $this->getHtml(); $paramGetH3 = '@\<h3\>(.+?)\<\/h3\>@'; preg_match_all($paramGetH3, $html, $resultH3); return $resultH3[1]; } }
mainSlow.php
<?php // require(dirname(__FILE__)."\define.php"); require(dirname(__FILE__).'\\'."function.php"); require(dirname(__FILE__)."\classSlow.php"); $objScrape = new ClassScrape; $pageNo = '2016/03/07/post-508/'; $objScrape->setData($pageNo); //実行 $timeStart = microtime(true); $resultH2 = $objScrape->getH2(); $resultH3 = $objScrape->getH3(); //debug debug_time('scrapeTime', $timeStart); debug('H2', $resultH2); debug('H3', $resultH3);
実行結果
//--- debug ---// // scrapeTime 開始時間: 2016-03-17 13:36:01 終了時間: 2016-03-17 13:36:10 処理時間:9.3[s] //--- debug ---// // H2 H2= array(7) { [0]=> string(12) "配列とは" [1]=> string(18) "配列の作り方" [2]=> string(12) "連想配列" [3]=> string(15) "多次元配列" [4]=> string(28) "私流 配列はこう書く" [5]=> string(30) "私が嵌った多次元配列" [6]=> string(9) "まとめ" } //--- debug ---// // H3 H3= array(14) { [0]=> string(33) "データを入れる箱の総称" [1]=> string(66) "key(キー) と value(値) の区別が付けば使いこなせる" [2]=> string(20) "$hairetsu = array();" [3]=> string(27) "もう少し試してみる" [4]=> string(13) "2次元配列" [5]=> string(57) "結局は キーに入れたdataが配列 ということ" [6]=> string(36) "キーを指定して操作できる" [7]=> string(33) "array() じゃなく [] を使う" [8]=> string(24) "コードの見やすさ" [9]=> string(32) "配列名は $ar を冠にする" [10]=> string(31) "困ったら var_dump しよう" [11]=> string(66) "配列としてデータを取得したいのならそう書こう" [12]=> string(72) "多次元にしたいなら、多次元に見えるように定義する" [13]=> string(12) "関連記事" }
遅い原因はメソッドの多重呼び出し
処理時間におよそ 9秒 かかっていますね。
classSlow.phpファイルの
-31行目
-40行目
に注目です。
ここではメソッド内にsleepを含む
-getHtml()
を呼び出しています。
mainSlow.phpファイル17,18行目で
$resultH2 = $objScrape->getH2(); $resultH3 = $objScrape->getH3();
2回sleepを掛けているのと同義です。
つまり、
-getHtml() を呼び出した回数分 sleep がかかる
ということです。
getHtml()は何度呼びだされても同じ内容なので、できれば1回だけ呼び出してその後の処理を変えたいところ。
改善例
次に改善例を出します。
classFast.php
<?php class ClassScrape{ //property private $pageNo; //const const TIMESLEEP = '2';//sleep時間 //constructor // function __construct(){ // $this->pageNo= '000000';//初期化 // } // public function setData($setPageNo){ // $this->pageNo = $estPageNo; // } public function getHtml($pageNo){ $urlBase = 'https://kengotakimoto.com/'; $url = $urlBase.$pageNo; sleep(self::TIMESLEEP);// 待ち時間 $html = file_get_contents($url); return $html; } public function getH2($setHtml){ // $html = $this->getHtml(); $paramGetH2 = '@\<h2\>(.+?)\<\/h2\>@'; preg_match_all($paramGetH2, $setHtml, $resultH2); return $resultH2[1]; } public function getH3($setHtml){ // $html = $this->getHtml(); $paramGetH3 = '@\<h3\>(.+?)\<\/h3\>@'; preg_match_all($paramGetH3, $setHtml, $resultH3); return $resultH3[1]; } }
main.php
<?php // require(dirname(__FILE__)."\define.php"); require(dirname(__FILE__).'\\'."function.php"); require(dirname(__FILE__)."\classFast.php"); $objClass = new ClassScrape; $urlBase = 'https://kengotakimoto.com/'; $pageNo = '2016/03/07/post-508/'; $url = $urlBase.$pageNo; //実行 $timeStart = microtime(true); $html = $objClass->getHtml($pageNo); $resultH2 = $objClass->getH2($html); $resultH3 = $objClass->getH3($html); //debug debug_time('scrapeTime', $timeStart); debug('H2', $resultH2); debug('H3', $resultH3);
実行結果
//--- debug ---// // scrapeTime 開始時間: 2016-03-17 13:36:00 終了時間: 2016-03-17 13:36:03 処理時間:3.3[s] //--- debug ---// // H2 H2= array(7) { [0]=> string(12) "配列とは" [1]=> string(18) "配列の作り方" [2]=> string(12) "連想配列" [3]=> string(15) "多次元配列" [4]=> string(28) "私流 配列はこう書く" [5]=> string(30) "私が嵌った多次元配列" [6]=> string(9) "まとめ" } //--- debug ---// // H3 H3= array(14) { [0]=> string(33) "データを入れる箱の総称" [1]=> string(66) "key(キー) と value(値) の区別が付けば使いこなせる" [2]=> string(20) "$hairetsu = array();" [3]=> string(27) "もう少し試してみる" [4]=> string(13) "2次元配列" [5]=> string(57) "結局は キーに入れたdataが配列 ということ" [6]=> string(36) "キーを指定して操作できる" [7]=> string(33) "array() じゃなく [] を使う" [8]=> string(24) "コードの見やすさ" [9]=> string(32) "配列名は $ar を冠にする" [10]=> string(31) "困ったら var_dump しよう" [11]=> string(66) "配列としてデータを取得したいのならそう書こう" [12]=> string(72) "多次元にしたいなら、多次元に見えるように定義する" [13]=> string(12) "関連記事" }
実行結果がおよそ 3秒 になりましたね。
先程の例よりも倍近く処理速度が向上しています。
ここでやったのは
-無駄な呼び出しをやめたこと
です。
何度呼び出しても同じ結果しか得られないメソッドなら1回だけ呼び出す
getHtml()は何度呼び出しても同じ結果が得られます。
でも、呼び出した回数だけsleepがかかってします。
(getHtml() 呼び出した回数) x (sleep時間) = (無駄な処理時間)
です。
なので、改善案では getHtml()はメインファイルで1回だけ呼び出しています。
それをローカル変数$htmlに入れて,その後H2,H3のメソッドを呼び出す形にしました。
$html = $objClass->getHtml($pageNo); $resultH2 = $objClass->getH2($html); $resultH3 = $objClass->getH3($html);
オブジェクト指向自体は非常に便利ですが、呼び出したメソッドがどんな動作をしているのかを把握しないと無駄な処理時間をかけることになってしまいます。
注意しましょう。
まとめ
メインファイルに書くコードと処理速度を考える
今回の記事は私が処理速度で困ったときの例を題材にしています。
当時は始めてオブジェクト指向を勉強してclassを使い始めました。
当時と言っても1,2ヶ月前なのですが・・。
なんでもかんでもclassメソッドを定義すれば確かにメインファイルはすっきりします。
でも、毎回メソッドを呼び出すことで無駄な処理が増えていました。
今回の例なら htmlの取得回数 ですね。
扱うデータ量が増えると数時間規模の処理時間になってしまいます。
細かいことですが、自分が気になった箇所は一つ一つ潰していきましょう。
今回は以上です。