本当に怖いパフォーマンスが悪い実装 #phpcon2013
DESCRIPTION
(PHPカンファレンス2013での発表内容です) Yahoo! JAPANほどの大規模サイトにおいては、小さなコードでも圧倒的に使われることで大量のコストを生み出します。実例を交えて非効率な実装と、その改善例を紹介します。TRANSCRIPT
![Page 1: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/1.jpg)
本当に恐い
パフォーマンスが悪い実装
Masakazu Nagaya
2013年09月14日(土曜日)
![Page 2: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/2.jpg)
Overview
• 大規模サイトでパフォーマンスを著しく劣化させる非効率な実装例や、その改善例を紹介します。
2
![Page 3: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/3.jpg)
アジェンダ
• はじめに
• パフォーマンスが悪い実装の紹介
• 失敗を繰り返さないために
• まとめ
3
![Page 4: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/4.jpg)
はじめに
4
![Page 5: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/5.jpg)
誰でも失敗する
• プログラムを書く全ての人間がスーパープログラマーではない
• 常に完璧で失敗をしない人間はいない
• 失敗は必ず発生する
5
![Page 6: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/6.jpg)
大切なこと
• 失敗から目をそむけない
• 失敗を隠さない(共有する)
• 失敗を繰り返さない
6
![Page 7: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/7.jpg)
パフォーマンスが悪い実装の紹介
7
![Page 8: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/8.jpg)
その1
• リソースの確保と解放のタイミングと回数に要注意
8
![Page 9: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/9.jpg)
問題の実装
9
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17
<?php class BlackListDB { const DBPATH = "/tmp/db.gdbm"; public function isBlock($id) { $dbh = dba_open(self::DBPATH, "r", "gdbm"); if ($dbh === false) { return null; } $ret = dba_exists($id, $dbh); dba_close($dbh); return $ret; } }
![Page 10: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/10.jpg)
問題点
10
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17
<?php class BlackListDB { const DBPATH = "/tmp/db.gdbm"; public function isBlock($id) { $dbh = dba_open(self::DBPATH, "r", "gdbm"); if ($dbh === false) { return null; } $ret = dba_exists($id, $dbh); dba_close($dbh); return $ret; } }
![Page 11: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/11.jpg)
改善した実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<?php class BlackListDB { const DBPATH = "/tmp/db.gdbm"; private $_dbh = null; function __construct() { $this->_dbh = dba_open(self::DBPATH, "r", "gdbm"); } public function isBlock($id) { if ($this->_dbh === false) { return null; } return dba_exists($id, $this->_dbh); } function __destruct() { dba_close($this->_dbh); } }
11
![Page 12: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/12.jpg)
ポイント
• isBlock()の数だけopenされると遅くなる
• openの処理コストも内部でシステムコール(open/mmap)を呼ぶので大きい
• 無駄な処理を減らす
12
![Page 13: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/13.jpg)
検証方法
• テストコードをサーバ上に配置
ツールによる負荷テストを実施
13
1 2 3 4 5 6 7 8
<?php $num = isset($_REQUEST["num"]) ? intval($_REQUEST["num"]) : 128; $obj = new BlackListDB(); for($i = 0;$i < $num; $i++) { $id = "dummy_id_".$i; printf("%s => %d¥ n", $id, $obj->isBlock($id)); }
![Page 14: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/14.jpg)
比較
14
![Page 15: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/15.jpg)
更なる改善
• リクエスト毎に毎回Open/Closeするのはもったいない
15
![Page 16: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/16.jpg)
更に改善した実装
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18
<?php class BlackListDB { const DBPATH = "/tmp/db.gdbm"; private $_dbh = null; function __construct() { $this->_dbh = dba_popen(self::DBPATH, "r", "gdbm"); } public function isBlock($id) { if ($this->_dbh === false) { return null; } return dba_exists($id, $this->_dbh); } }
16
![Page 17: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/17.jpg)
Persistent Resources とは
• プロセス単位でオープンしたリソースを永続的に保持し、次回のリクエストで再利用する
• Persistent Resourcesの例 sqlite_popen(), pfsockopen(), oci_pconnect(), mysql_pconnect() など
17
(PHP5.5ではmysql_pconnectは廃止されます。代替の関数を利用すべきです)
![Page 18: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/18.jpg)
Life Cycle
18
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
Apache Child Process
リソース確保
再利用
再利用
リソース解放
![Page 19: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/19.jpg)
その2
• 大量のdefineによる問題
19
![Page 20: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/20.jpg)
問題の実装
1 2 3 4 5
128 129 130
<?php define(“XXXXX_ERR", 0); define("XXXXX_OK", 1); define("XXXXX_WANT_MORE_TEXT", 2); define("XXXXX_NO_MORE_TEXT", 3); // snip define("XXXXX_YURAGI", 0x0002); define("XXXXX_DOGIGO", 0x0004); define("XXXXX_USRDEF", 0x0040);
20
![Page 21: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/21.jpg)
問題点
21
• defineは処理コストが大きく、リクエスト毎にdefineが実行される
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
定義処理
定義処理
定義処理
![Page 22: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/22.jpg)
改善した実装
234 235 236 237 238 239 240 241 242 243
348 349 350 351
PHP_MINIT_FUNCTION(xxxxx) { /* If you have INI entries, uncomment these lines ZEND_INIT_MODULE_GLOBALS(xxxxx, xxxxx_init_globals, NULL); REGISTER_INI_ENTRIES(); */ REGISTER_LONG_CONSTANT( "XXXXX_ERR", 0, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_OK", 1, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_WANT_MORE_TEXT", 2, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_NO_MORE_TEXT", 3, CONST_CS|CONST_PERSISTENT ); // snip REGISTER_LONG_CONSTANT( "XXXXX_KUGIRI", 0x0001, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_YURAGI", 0x0002, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_DOGIGO", 0x0004, CONST_CS|CONST_PERSISTENT ); REGISTER_LONG_CONSTANT( "XXXXX_USRDEF", 0x0040, CONST_CS|CONST_PERSISTENT );
22
![Page 23: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/23.jpg)
ポイント
23
• エクステンションで利用する定数はエクステンションの起動時(MINIT)で定義する
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
定義処理
![Page 24: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/24.jpg)
比較
24
![Page 25: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/25.jpg)
その他の改善方法
• hidefを活用するのが良い
25
![Page 26: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/26.jpg)
hidefとは
• iniファイルから定数を一括定義する
• MINITの処理で定数を定義する
• リクエスト毎に処理しないので効率的
26
![Page 27: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/27.jpg)
その3
• ホスト名取得(exec)による問題
27
![Page 28: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/28.jpg)
問題の実装
28
1 2 3
<?php $hostname = exec("hostname"); printf("%s¥ n", $hostname);
![Page 29: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/29.jpg)
問題点
• 激おこぷんぷんまるレベル
29
![Page 30: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/30.jpg)
問題点
• プロセスの生成コストは非常に大きい
• Preforkの設計努力も台無し
• セキュリティ的な観点からも外部コマンドが実行はすべきでない
30
![Page 31: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/31.jpg)
改善した実装
31
1 2 3
<?php $hostname = gethostname(); printf("%s¥ n", $hostname);
![Page 32: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/32.jpg)
ポイント
32
• PHP5.3以降でサポートされた標準のgethostname()を使用する
• 外部コマンドは絶対に使わない
![Page 33: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/33.jpg)
比較
33
![Page 34: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/34.jpg)
失敗を繰り返さないために
34
![Page 35: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/35.jpg)
継続的なテストの実行の必要性
• 良い習慣はツールの支援なしに継続することは難しい
• どんな賢人であっても魔が差すとテストを省くときがある
35
![Page 36: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/36.jpg)
ツールの支援で解決する
• Yahoo! JAPANで標準的に使われている
36
![Page 37: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/37.jpg)
例えばパフォーマンステストを自動化し結果を可視化する
37
![Page 38: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/38.jpg)
大切なこと
• コミット、ビルド、テスト、リリースのプロセスを自動化するためにツールを活用し、人に依存する過ちを減らすこと
38
![Page 39: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/39.jpg)
まとめ
39
![Page 40: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/40.jpg)
まとめ
• 実行回数が多くなる処理に注意しよう
• どんな達人でも必ずミスをするし
• どんな賢人でも魔が差すとテストを省く
• ツールの支援による継続的なテストは課題解決のための良い方法の1つです
40
![Page 41: 本当に怖いパフォーマンスが悪い実装 #phpcon2013](https://reader035.vdocuments.net/reader035/viewer/2022081717/54b768314a795957768b462b/html5/thumbnails/41.jpg)
41