[転送] PHP アクセラレーション PHP APC 簡単な分析
PHP APC は 2 つのキャッシュ関数を提供します。1 つはオペコード (ターゲット ファイル) のキャッシュであり、これを apc_compiler_cache と呼びます。同時に、PHP 開発者がユーザー データをメモリに常駐させるためのインターフェイス (apc_user_cache と呼ばれます) も提供します。ここでは主に php-apc の設定を制御します。
PHP APC のインストール
テスト環境として、CentOS5.3 (2.6.18-128.el5PAE) + Apache2.0 (prefork) + php5.2 を使用しています。 pecl apc に移動して、APC-3.0.19.tgz をダウンロードできます
# tar -xzvf APC-3.0.19.tgz
#cd APC-3.0.19
# /usr/bin/phpize
# ./configure --enable-apc --enable-mmap --enable-apc-spinlocks --disable-apc-pthreadmutex
#make
#make install
ログイン後にコピー
注: ここでは mmap をサポートし、スピンロックを使用します。 Spinlocks は Facebook によって推奨されており、APC 開発者によって推奨されているロック メカニズムでもあります。
PHP APC 設定パラメータ
使用しているシステム環境がテスト環境と同じ場合は、/etc/php.d ディレクトリにファイル apc.ini を作成し、関連する設定を /etc/php.d/apc.ini ファイルに追加します。ここでは、一般的に使用される構成をいくつか選択して説明しました。関連する設定をまとめて説明します。
apc.enabled=1
apc.enabled のデフォルト値は 1 ですが、0 に設定すると APC を無効にすることができます。 0 に設定する場合は、extension=apc.so もコメントアウトします (これによりメモリ リソースを節約できます)。 APC 機能が有効になると、オペコードは共有メモリにキャッシュされます。
apc.shm_segments = 1
apc.shm_size = 30
APC はデータをメモリにキャッシュするため、そのメモリ リソースを制限する必要があります。これら 2 つの構成により、APC が使用できるメモリ空間が制限される可能性があります。 apc.shm_segments は使用する共有メモリ ブロックの数を指定し、apc.shm_size は共有メモリ空間のサイズを M 単位で指定します。したがって、APC に許可されるメモリ サイズは、apc.shm_segments * apc.shm_size = 30M である必要があります。共有メモリ空間のサイズを調整できます。もちろん、共有メモリの最大サイズはオペレーティング システムによって制限されます。つまり、/proc/sys/kernel/shmmax のサイズを超えることはできません。そうしないと、共有メモリの作成時に APC が失敗します。 apc.shm_size が上限に達した場合、apc.shm_segments を設定して、APC がより多くのメモリ領域を使用できるようにすることができます。メモリ空間を使用するために APC が呼び出される場合は、最初に apc.shm_size をフィルタリングし、次に apc.shm_segments をフィルタリングすることをお勧めします。具体的な値は、apc.phpの監視条件に基づいて計画および調整できます。各調整では、apc.so モジュールを再ロードできるように httpd デーモンを再起動する必要があることに注意してください。 httpd デーモンが起動すると、apc.so モジュールがロードされます。 apc.so がロードされて初期化されると、指定されたサイズのメモリが mmap を通じて割り当てられます (apc.shm_size * apc.shm_segments)。さらに、ここでは匿名メモリ マッピング方法が使用されており、特別なデバイス /dev/zero をマッピングすることにより、ゼロで埋められた「大きな」メモリが APC 管理用に提供されます。
上記のステートメントを検証するために、apc.ini 設定をコメント アウトし、apc.so モジュールによって初期化された割り当てられたメモリ領域を観察する次の PHP スクリプトを作成しました。
<?php
//@file: apc_load.php
if (!extension_loaded('apc')) {
dl('apc.so'); #加载apc.so模块
echo posix_getpid(); #//输出当前进程的pid,我这里这里输出的是14735
ob_flush();
flush();
sleep(3600); #让进程进入休眠状态.这样,我们可以观察内存分配情况
}
?>
ログイン後にコピー
#strace -p `cat /var/run/httpd.pid`
open("/var/www/html/apc_load.php" 、O_RDONLY) = 13
...
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000
...
nanosleep({3600, 0 },
赤い部分では、mmap システム カーネル呼び出しを通じて 30M (31457280/1024/1024) のメモリ空間が割り当てられていることがわかります。PROT_READ|PROT_WRITE は、メモリ空間が読み取りと書き込みに使用できることを示しています。メモリ空間が他のプロセスと共有されていることを示します。つまり、他のプロセスもこのメモリ空間を管理でき、fd=-1 は匿名マッピングを意味します。特別なデバイス /dev/zero がここにマップされているため、最後の 0 はオフセットがないことを意味し、プロセス イメージ ファイル
#cat /proc/14735 /smaps
b5ce7000-b7ae7000 rw-s 00000000 00:08 633695 /dev/zero (削除済み)
サイズ: 30720 kB
Rss: 44 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 44 kB
開始アドレス 0xb5ce7000 が、上記の mmap システム カーネル コールによって返されたアドレスと同じであることが簡単にわかります。このメモリ ブロックは次のとおりです。 rw を書き込み、他のプロセスと共有します。また、/dev/zero はマッピング ファイルです。このうち、size はプロセスが使用できるメモリ領域を表し、rss は実際に割り当てられたメモリを表します。実際に割り当てられた 44kb メモリは、現在のプロセス自体によって割り当てられていることがわかります (
apc.num_files_hint = 1000
apc.user_entries_hint = 4096
)。这二配置指定apc可以有多少个缓存条目。apc.num_files_hint说明你估计可能会有多少个文件相应的opcodes需要被缓成,即大约可以有多少个apc_compiler_cache条目。另外apc.user_entries_hint则说明你估计可能会有多少个 apc_userdata_cache条目需要被缓存。如果项目中不使用apc_store()缓存用户数据的话,该值可以设定得更小。也就是说 apc.num_files_hint与apc.user_entries_hint之和决定了APC允许最大缓存对象条目的数量。准确地设置这二个值可以得到最佳查询性能。当然,如果你不清楚要进行多少缓存(缓存对象实例)的情况下,你可以不必修改这二项配置。
其中apc.user_entries_hint要根据项目实际开发使用了apc_store()条目估计其值大小。相较而言,apc.num_files_hint可以通过find命令,更容易地估计其大小。比如我们的web根目是/var/vhosts,则使用下面的 find命令可以大致地统计当前apc.num_files_hint数目.
#find /var/vhosts \( -name “*.php” -or -name “*.inc” \) -type f -print |wc -l
1442
apc.stat = 1
apc.stat_ctime = 0
这二个参数,只跟apc_compiler_cache缓存相关,并不影响apc_user_cache。我们前面提到过 apc_complier_cache,它缓存的对象是php源文件一一对应的opcodes(目标文件)。PHP源文件存放在磁盘设备上,与之相对应的 Opcodes目标文件位置内存空间(共享内存),那么当php源文件被修改以后,怎么通知更新内存空间的opcodes呢?每次接收到请求后,APC都会去检查打开的php源文件的最后修改时间,如果文件的最后修改时间与相应的内存空间缓存对象记录的最后修改时间不一致的话,APC则会认为存放在内存空间的Opcode目标文件(缓存对象)已经过期了,acp会将缓存对象清除并且保存新解析得到的Opcode。我们关心的是,即便没有更新任何php源文件,每次接受到http请求后,APC都会请求系统内核调用stat()来获取php源文件最后修改时。我们可以通过将apc.stat设置为0,要求 APC不去检查Opcodes相对应的php源文件是否更新了。这样可以获得最佳的性能,我们也推荐这么做。不过,这样做有一点不好的就是,一旦有PHP 源文件更新了之后,需要重启httpd守护进程或者调用apc_cache_clear()函数清空APC缓存来保证php源文件与缓存在内存空间的 Opcodes相一致。
<?php
define('ROOTP', dirname(__FILE__) . '/');
include(ROOTP . 'i1.php');
require(ROOTP . 'i2.php');
include_once(ROOTP . 'i3.php');
require_once(ROOTP . 'i4.php');
require(ROOTP . 'i5.php');
include(ROOTP . 'i6.php');
?>
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
stat64("/var/www/html/i1.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i2.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/var/www/html/i3.php", O_RDONLY) = 12
stat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
open("/var/www/html/i4.php", O_RDONLY) = 12
stat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i5.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
stat64("/var/www/html/i6.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0
chdir("/tmp")
ログイン後にコピー
= 0
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
open("/var/www/html/i3.php", O_RDONLY) = 12
open("/var/www/html/i4.php", O_RDONLY) = 12
chdir("/tmp") = 0
对比可见,当apc.stat=0时,省了很多系统内核调用,我们没有看到系统内核调用stat64了。其中,i3.php和i4.php分别是 php的include_once和require_once函数调用,它要交给fstat()系统内核调用来检查文件是否打开过。单从性能角度出发的话,require比require_once性能更佳。
设置apc.stat_ctime的意义并是很大。如果apc.stat_ctime值为1时,仅当php源文件的创建时间(ctime)大于 php源文件的最后修改时间(mtime)时,缓存对象的mtime时间会被php源文件的ctime所代替,否则缓存对象的mtime依然记录为php 源文件的mtime。这样做是防止通过cvs, svn或者rsync等工具刷新php源文件的mtime,这样会导致APC通过比对php源文件的创建时间ctime来决定缓存对象有没有过期。我们推荐该保持默认值,即apc.stat_ctime = 0
apc.ttl=0
apc.user_ttl=0
缓存对象的生命周期。其中ttl表示Time To Live,意味着指定时间后缓存对象会被清除。其中0表示永不过期。我们前面提过,APC能缓存的条目是受限定的,如果你把ttl设置永不过期的话,当缓存条目已满或者缓存空间不够,之后的缓存都将失败。
其中apc.ttl作用于apc_compiler_cache。当apc.ttl大于0时,每次请求都会对比这次的请求时间与上一次请求时间之差是不是大于apc.ttl,如果大于apc.ttl,则会被认缓存条目过期了,会被清理。
さらに興味深いのは、主に apc_user_cache キャッシュに作用する apc.user_ttl です。このタイプのキャッシュは、apc_store($key, $var, $ttl = 0) によって作成されたキャッシュ オブジェクトであることがわかっています。私たちは、関数 apc_store() で指定された $ttl と php.ini で設定された apc.user_ttl の間の類似点と相違点のほうに関心があります。これらは apc_userdata_cache キャッシュにも作用するためです。分析の結果、次のことがわかりました。 apc_user_cache キャッシュの有効期限を判断する基準は、apc.user_ttl が 0 より大きく、この http リクエスト時間と最後の http リクエスト時間の差が apc.user_ttl より大きい場合、対応するキャッシュ エントリです。期限切れとみなされます。または、user.data.ttl (php 関数 apc_store() で指定された $ttl) が 0 より大きく、この http リクエスト時間とキャッシュ オブジェクト作成時間 ctime の差が user より大きいです。 data.ttl のキャッシュ エントリも期限切れとみなされ、クリアされます。
プロジェクトが比較的安定していて、apc.stat が 0 に設定されている場合にお勧めします。同時に、apc.shm_size と apc.num_files_hint が適切に設定されている場合は、apc.ttl を 0 に設定することをお勧めします。つまり、httpd デーモンが再起動されるか、関数 apc_cache_clear() が呼び出されてキャッシュがクリアされるまで、apc_compiler_cache は決してリサイクルされません。 apc.user_ttl については、0 に設定することをお勧めします。開発者が apc_store() 関数を呼び出すときに、$ttl を設定してキャッシュ オブジェクトのライフ サイクルを指定します。
apc.slam_defense=0
apc.write_lock=1
apc.file_update_protection=2
これら 3 つの構成を一緒に説明する理由は、それらの意味が非常に似ているためです。その中で、apc.file_update_protection は時間単位である秒が最もよく理解されています。現在の http リクエスト時刻と php ソース ファイルの最適変更時刻 mtime の差が apc.file_update_protection 時刻より小さい場合、APC は次回アクセスするまで php ソース ファイルとそれに対応するオペコードをキャッシュしません。は php と同じです。 ソース ファイルの最終変更時刻が apc.file_update_protection 時刻よりも大きいため、対応するオペコードが共有メモリ領域にキャッシュされます。この利点は、変更しているソース ファイルにユーザーがアクセスするのが簡単ではないことです。開発環境ではこの値を大きく設定することをお勧めしますが、運用環境ではデフォルト値のままにすることをお勧めします。
Web サイトに大量の同時実行性がある場合、http デーモンによってフォークされた複数の子プロセスが同じオペコードを同時にキャッシュする可能性があります。 apc.slam_defense を通じて、この問題が発生する可能性を減らすことができます。たとえば、apc.slam_defense 値が 60 に設定されている場合、キャッシュされていないオペコードが発生した場合、100 回中 60 回はキャッシュされません。同時実行性が低い Web サイトの場合は、この値を 0 に設定することをお勧めします。同時実行性が高い Web サイトの場合は、統計に基づいてこの値を適切に調整できます。また、apc.write_lock がブール値である場合、この値が 1 に設定されている場合、複数のプロセスが同じオペコードを同時にキャッシュすると、最初のプロセス キャッシュのみが有効になり、その他は無効になります。 apc.write_lock 設定により、キャッシュ書き込み競合の発生が効果的に回避されます。
apc.max_file_size=1M
apc.filters = NULL
apc.cache_by_default=1
これら 3 つの構成は、すべてキャッシュを制限するために使用されるため、まとめられています。このうち、apc.max_file_size は、php ソースファイルが 1M を超える場合、対応するオペコードがキャッシュされないことを意味します。 Apc.filters は、ファイル フィルター リストをカンマ (,) で区切って指定します。 apc.cache_by_default が 1 の場合、apc.filters リストで指定されたファイル名と一致するファイルはキャッシュされません。対照的に、apc.cache_by_default が 0 に等しい場合、acp.filters リストで指定されたファイルと一致するファイルのみがキャッシュされます。
概要
1. Spinlocks ロック メカニズムを使用すると、最高のパフォーマンスを実現できます。
2. APC は、APC キャッシュを監視および管理するための apc.php を提供します。管理者名とパスワードを変更することを忘れないでください
3. APC は、デフォルトで mmap 匿名マッピングを通じて共有メモリを作成し、キャッシュ オブジェクトはこの「大きな」メモリ領域に保存されます。共有メモリは APC 自体によって管理されます
4. 統計を通じて apc.shm_size、apc.num_files_hints、および apc.user_entries_hint の値を調整する必要があります。最適な
5 までは、apc.stat = 0 の方がパフォーマンスが向上することは認めます。
6. PHP の事前定義定数は、apc_define_constants() 関数を使用できます。ただし、APC 開発者によると、pecl Hidef の方がパフォーマンスが優れており、例外定義をスローするだけなので非効率的です。
7. apc_store() 関数。システム設定などの PHP 変数の場合、ライフサイクルはアプリケーション全体です (httpd デーモンが終了するまで)。Memcached よりも APC を使用する方が良いです。結局のところ、ネットワーク伝送プロトコル tcp は使用しないでください。
8. APC は、頻繁に変更されるユーザーデータを apc_store() 関数を通じてキャッシュするのには適しておらず、いくつかの奇妙な現象が発生します。