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

Post on 15-Jan-2015

27.655 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

本当に恐い

パフォーマンスが悪い実装

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

top related