PHP アクセラレーションの簡単な分析 PHP APC
?
原文: http://www.perfgeeks.com/?p=298
PHP APC は 2 つのキャッシュ関数を提供します。1 つはオペコード (ターゲット ファイル) のキャッシュであり、これを apc_compiler_cache と呼びます。同時に、PHP 開発者がユーザー データをメモリに常駐させるためのいくつかのインターフェイス (apc_user_cache と呼ばれます) も提供します。ここでは主に 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 開発者によって推奨されているロック メカニズムでもあります。
使用しているシステム環境が私のテスト環境と同じ場合は、/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 スクリプトを作成しました。
<div class="codesnip-container"> <pre class="php codesnip"><span style="color: #000000; font-weight: bold;" class="kw2"><?php</span> <span style="color: #808080;" class="co1">//@file: apc_load.php</span> <span style="color: #a1a100;" class="kw1">if</span> <span style="color: #66cc66;" class="br0">(</span><span class="sy0">!</span><span style="color: #000066;" class="kw3">extension_loaded</span><span style="color: #66cc66;" class="br0">(</span><span class="st_h">'apc'</span><span style="color: #66cc66;" class="br0">)</span><span style="color: #66cc66;" class="br0">)</span> <span style="color: #66cc66;" class="br0">{</span> <span style="color: #000066;" class="kw3">dl</span><span style="color: #66cc66;" class="br0">(</span><span class="st_h">'apc.so'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #808080;" class="co2">#加载apc.so模块 </span> <span style="color: #a1a100;" class="kw1">echo</span> <span style="color: #000066;" class="kw3">posix_getpid</span><span style="color: #66cc66;" class="br0">(</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #808080;" class="co2">#//输出当前进程的pid,我这里这里输出的是14735 </span> <span style="color: #000066;" class="kw3">ob_flush</span><span style="color: #66cc66;" class="br0">(</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #000066;" class="kw3">flush</span><span style="color: #66cc66;" class="br0">(</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #000066;" class="kw3">sleep</span><span style="color: #66cc66;" class="br0">(</span>3600<span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #808080;" class="co2">#让进程进入休眠状态.这样,我们可以观察内存分配情况 </span> <span style="color: #66cc66;" class="br0">}</span> <span class="sy1">?></span>
#strace -p `cat /var/run/httpd.pid` open("/var/www/html/apc_load.php", O_RDONLY) = 13 ... <span style="color: red;">mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000</span> ... nanosleep({3600, 0},
赤い部分が見えます。 30M (31457280/1024/1024) メモリ空間は、mmap システム カーネル コールを通じて割り当てられます。 PROT_READ|PROT_WRITE は、メモリ空間が読み取りおよび書き込みに使用できることを示します。 MAP_SHARED は、メモリ空間が他のプロセスと共有されることを意味します。つまり、他のプロセスも apc.php を通じてこのメモリ空間を管理でき、この設定の恩恵を受けることができます。 MAP_ANONYMOUS は匿名マッピングを表します。このうち、fd=-1 は特殊なデバイス /dev/zero がここにマッピングされているため、無視されることを意味します。最後の 0 はオフセットがないことを意味します。プロセス イメージ ファイル
#cat /proc/14735/smaps
b5ce7000-b7ae7000 rw-s 00000000 00:08 633695 /dev/zero (deleted) Size: 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 はマッピング ファイルで、ファイル ノードは 633695 です。このうち、size はプロセスが使用できるメモリ空間を表し、rss は実際に割り当てられたメモリ空間を表します。Private_Dirty から、実際に割り当てられた 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相一致。
<div class="codesnip-container"> <pre class="php codesnip"><span style="color: #000000; font-weight: bold;" class="kw2"><?php</span> <span style="color: #000066;" class="kw3">define</span><span style="color: #66cc66;" class="br0">(</span><span class="st_h">'ROOTP'</span><span class="sy0">,</span> <span style="color: #000066;" class="kw3">dirname</span><span style="color: #66cc66;" class="br0">(</span><span style="color: #f63333;" class="kw4">__FILE__</span><span style="color: #66cc66;" class="br0">)</span> <span class="sy0">.</span> <span class="st_h">'/'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">include</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i1.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">require</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i2.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">include_once</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i3.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">require_once</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i4.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">require</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i5.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span style="color: #a1a100;" class="kw1">include</span><span style="color: #66cc66;" class="br0">(</span>ROOTP <span class="sy0">.</span> <span class="st_h">'i6.php'</span><span style="color: #66cc66;" class="br0">)</span><span class="sy0">;</span> <span class="sy1">?></span>
# 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_ttl で、これは主に apc_user_cache キャッシュで動作します。このタイプのキャッシュは、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 の方がパフォーマンスが優れているため、define を破棄するだけで非効率的です。
7. apc_store() 関数。システム設定などの PHP 変数の場合、ライフサイクルはアプリケーション全体です (httpd デーモンが終了するまで)。Memcached よりも APC を使用する方が良いです。結局のところ、ネットワーク伝送プロトコル tcp は使用しないでください。
8. APC は、頻繁に変更されるユーザーデータを apc_store() 関数を通じてキャッシュするのには適しておらず、いくつかの奇妙な現象が発生します。
?