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