本当に怖いパフォーマンスが悪い実装 #phpcon2013

592 Views

September 17, 13

スライド概要

(PHPカンファレンス2013での発表内容です) Yahoo! JAPANほどの大規模サイトにおいては、小さなコードでも圧倒的に使われることで大量のコストを生み出します。実例を交えて非効率な実装と、その改善例を紹介します。

profile-image

2023年10月からSpeaker Deckに移行しました。最新情報はこちらをご覧ください。 https://speakerdeck.com/lycorptech_jp

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

本当に恐い パフォーマンスが悪い実装 Masakazu Nagaya 2013年09月14日(土曜日)

2.

Overview • 大規模サイトでパフォーマンスを著しく劣 化させる非効率な実装例や、その改善例 を紹介します。 2

3.

アジェンダ • はじめに • パフォーマンスが悪い実装の紹介 • 失敗を繰り返さないために • まとめ 3

4.

はじめに 4

5.

誰でも失敗する • プログラムを書く全ての人間がスーパー プログラマーではない • 常に完璧で失敗をしない人間はいない • 失敗は必ず発生する 5

6.

大切なこと • 失敗から目をそむけない • 失敗を隠さない(共有する) • 失敗を繰り返さない 6

7.

パフォーマンスが悪い実装の紹介 7

8.

その1 • リソースの確保と解放のタイミングと回数 に要注意 8

9.
[beta]
問題の実装
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

10.
[beta]
問題点
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

11.
[beta]
改善した実装
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

12.

ポイント • isBlock()の数だけopenされると遅くなる • openの処理コストも内部でシステムコー ル(open/mmap)を呼ぶので大きい • 無駄な処理を減らす 12

13.
[beta]
検証方法
• テストコードをサーバ上に配置
ツールによる負荷テストを実施
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.

比較 14

15.

更なる改善 • リクエスト毎に毎回Open/Closeするのは もったいない 15

16.
[beta]
更に改善した実装
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

17.

Persistent Resources とは • プロセス単位でオープンしたリソースを永 続的に保持し、次回のリクエストで再利用 する • Persistent Resourcesの例 sqlite_popen(), pfsockopen(), oci_pconnect(), mysql_pconnect() など (PHP5. 17 ではmysql_pconnect は廃止されます。代替の関数を利用すべきです)

18.

Life Cycle pA acheChildProcess MINIT RINIT ScriptxE ecution RSHUTDOWN RINIT ScriptxE ecution RSHUTDOWN 再利用 RINIT ScriptxE ecution RSHUTDOWN 再利用 MSHUTDOWN 18 リソース確保 リソース解放

19.

その2 • 大量のdefineによる問題 19

20.

問題の実装 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

21.

問題点 • defineは処理コストが大きく、リクエスト毎 にdefineが実行される MINIT RINIT ScriptxE ecution RSHUTDOWN RINIT ScriptxE ecution RSHUTDOWN 定義処理 RINIT ScriptxE ecution RSHUTDOWN 定義処理 MSHUTDOWN 21 定義処理

22.

改善した実装 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 ); ); ); );

23.

ポイント • エクステンションで利用する定数はエクス テンションの起動時(MINIT)で定義する MINIT RINIT ScriptxE ecution RSHUTDOWN RINIT ScriptxE ecution RSHUTDOWN MSHUTDOWN 23 定義処理

24.

比較 24

25.

その他の改善方法 • hidefを活用するのが良い 25

26.

hidefとは • iniファイルから定数を一括定義する • MINITの処理で定数を定義する • リクエスト毎に処理しないので効率的 26

27.

その3 • ホスト名取得(exec)による問題 27

28.

問題の実装 1<?php 2$hostname = exec("hostname"); 3printf("%s¥n", $hostname); 28

29.

問題点 • 激おこぷんぷんまるレベル 29

30.

問題点 • プロセスの生成コストは非常に大きい • Preforkの設計努力も台無し • セキュリティ的な観点からも外部コマンド が実行はすべきでない 30

31.

改善した実装 1<?php 2$hostname = gethostname(); 3printf("%s¥n", $hostname); 31

32.

ポイント • PHP5.3以降でサポートされた標準の gethostname()を使用する • 外部コマンドは絶対に使わない 32

33.

比較 33

34.

失敗を繰り返さないために 34

35.

継続的なテストの実行の必要性 • 良い習慣はツールの支援なしに継続する ことは難しい • どんな賢人であっても魔が差すとテストを 省くときがある 35

36.

ツールの支援で解決する • Yahoo! JAPANで標準的に使われている 36

37.

例えばパフォーマンステストを自動化 し結果を可視化する 37

38.

大切なこと • コミット、ビルド、テスト、リリースのプロセ スを自動化するためにツールを活用し、 人に依存する過ちを減らすこと 38

39.

まとめ 39

40.

まとめ • 実行回数が多くなる処理に注意しよう • どんな達人でも必ずミスをするし • どんな賢人でも魔が差すとテストを省く • ツールの支援による継続的なテストは課 題解決のための良い方法の1つです 40