Post on 15-Jan-2015
description
本当に恐い
パフォーマンスが悪い実装
Masakazu Nagaya
2013年09月14日(土曜日)
Overview
• 大規模サイトでパフォーマンスを著しく劣化させる非効率な実装例や、その改善例を紹介します。
2
アジェンダ
• はじめに
• パフォーマンスが悪い実装の紹介
• 失敗を繰り返さないために
• まとめ
3
はじめに
4
誰でも失敗する
• プログラムを書く全ての人間がスーパープログラマーではない
• 常に完璧で失敗をしない人間はいない
• 失敗は必ず発生する
5
大切なこと
• 失敗から目をそむけない
• 失敗を隠さない(共有する)
• 失敗を繰り返さない
6
パフォーマンスが悪い実装の紹介
7
その1
• リソースの確保と解放のタイミングと回数に要注意
8
問題の実装
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; } }
問題点
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; } }
改善した実装
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
ポイント
• isBlock()の数だけopenされると遅くなる
• openの処理コストも内部でシステムコール(open/mmap)を呼ぶので大きい
• 無駄な処理を減らす
12
検証方法
• テストコードをサーバ上に配置
ツールによる負荷テストを実施
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)); }
比較
14
更なる改善
• リクエスト毎に毎回Open/Closeするのはもったいない
15
更に改善した実装
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
Persistent Resources とは
• プロセス単位でオープンしたリソースを永続的に保持し、次回のリクエストで再利用する
• Persistent Resourcesの例 sqlite_popen(), pfsockopen(), oci_pconnect(), mysql_pconnect() など
17
(PHP5.5ではmysql_pconnectは廃止されます。代替の関数を利用すべきです)
Life Cycle
18
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
Apache Child Process
リソース確保
再利用
再利用
リソース解放
その2
• 大量のdefineによる問題
19
問題の実装
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
問題点
21
• defineは処理コストが大きく、リクエスト毎にdefineが実行される
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
定義処理
定義処理
定義処理
改善した実装
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
ポイント
23
• エクステンションで利用する定数はエクステンションの起動時(MINIT)で定義する
MINIT
RINIT
Script Execution
RSHUTDOWN
RINIT
Script Execution
RSHUTDOWN
MSHUTDOWN
定義処理
比較
24
その他の改善方法
• hidefを活用するのが良い
25
hidefとは
• iniファイルから定数を一括定義する
• MINITの処理で定数を定義する
• リクエスト毎に処理しないので効率的
26
その3
• ホスト名取得(exec)による問題
27
問題の実装
28
1 2 3
<?php $hostname = exec("hostname"); printf("%s¥ n", $hostname);
問題点
• 激おこぷんぷんまるレベル
29
問題点
• プロセスの生成コストは非常に大きい
• Preforkの設計努力も台無し
• セキュリティ的な観点からも外部コマンドが実行はすべきでない
30
改善した実装
31
1 2 3
<?php $hostname = gethostname(); printf("%s¥ n", $hostname);
ポイント
32
• PHP5.3以降でサポートされた標準のgethostname()を使用する
• 外部コマンドは絶対に使わない
比較
33
失敗を繰り返さないために
34
継続的なテストの実行の必要性
• 良い習慣はツールの支援なしに継続することは難しい
• どんな賢人であっても魔が差すとテストを省くときがある
35
ツールの支援で解決する
• Yahoo! JAPANで標準的に使われている
36
例えばパフォーマンステストを自動化し結果を可視化する
37
大切なこと
• コミット、ビルド、テスト、リリースのプロセスを自動化するためにツールを活用し、人に依存する過ちを減らすこと
38
まとめ
39
まとめ
• 実行回数が多くなる処理に注意しよう
• どんな達人でも必ずミスをするし
• どんな賢人でも魔が差すとテストを省く
• ツールの支援による継続的なテストは課題解決のための良い方法の1つです
40
41