OPCache拡張PHP用拡張機能の概要





PHPは、実行する必要があるファイルをデフォルトでコンパイルするスクリプト言語です。 コンパイル中に、 オペコードを抽出して実行し、すぐに破棄します。 PHPはこのように設計されました。Rリクエストの実行に移ると、R-1リクエスト中に実行されたすべてのことを「忘れ」ます。



PHPコードが運用要求間で運用サーバー間で変更されることはほとんどありません。 したがって、コンパイル中は常に同じソースコードが読み取られるため、オペコードはまったく同じであると想定できます。 また、スクリプトごとに抽出すると、時間とリソースが無駄になります。







コンパイル時間が長いため、オペコードをキャッシュするための拡張機能が開発されました。 その主なタスクは、各PHPスクリプトを1回コンパイルし、生成されたオペコードを共有メモリにキャッシュして、運用プールのすべてのPHPワークフローがそれらを読み取って実行できるようにすることです(PHP-FPMが通常使用されます)。



その結果、言語の全体的なパフォーマンスが大幅に向上し、スクリプトの実行に少なくとも半分の時間がかかります(スクリプト自体に大きく依存します)。 PHPは同じスクリプトを何度もコンパイルする必要がないため、通常はさらに少なくなります。



アプリケーションが複雑になるほど、この最適化の効率は高くなります。 プログラムが、フレームワークベースのアプリケーションやWordpressのような製品など、多数のファイルを起動する場合、スクリプトの実行時間は10〜15倍短くなります。 実際のところ、PHPコンパイラは、構文を別の構文に変換し、記述内容を理解しようとし、結果のコードを何らかの方法で最適化して実行速度を上げる必要があるため、低速です。 そのため、コンパイラーは遅く、大量のメモリを消費します。 Blackfireのようなプロファイラーの助けを借りて、コンパイルの期間を予測できます。







OPCacheの概要



OPCacheのソースコードは2013年に開かれ、PHP 5.5.0のパッケージに含まれるようになりました。 それ以来、これはPHPでオペコードをキャッシュするための標準ソリューションです。 ここでは、他のソリューションについては検討しません。なぜなら、OPCacheを支持してサポートが打ち切られたAPCのみに精通しているからです。 つまり、以前にAPCを使用したことがある場合は、OPCacheを使用します。 現在、オペコードの問題をキャッシュするためのPHP開発者向けの推奨ソリューションです。 もちろん、必要に応じて他のツールを使用できますが、オペコードをキャッシュするために複数の拡張機能を同時にアクティブにすることはできません 。 これにより、PHPが確実にダウンします。



また、OPCacheのさらなる開発はPHP 7のフレームワーク内でのみ実行され、PHP 5では実行されないことに注意してください。この記事では、違いを確認できるように、両方のバージョンのOPCacheを検討します(大きすぎません)。



そのため、OPCacheは拡張機能、より正確にはzend拡張機能であり、バージョン5.5.0以降PHPソースコードに実装されています。 php.iniを介して通常のアクティベーションプロセスを使用してアクティベートする必要があります。 ディストリビューションについては、マニュアルをチェックして、PHPおよびOPCacheを理解してください。



1つの製品の2つの機能



OPCacheには2つの主要な機能があります。





OPCacheはコンパイラを実行してコードを取得およびキャッシュするため、このステップを使用してコードを最適化できます。 本質的に、私たちはさまざまなコンパイラの最適化について話しています。 OPCacheはマルチパスコンパイラオプティマイザーとして機能します。







OPCache内部



OPCacheが内部でどのように機能するかを見てみましょう。 コードを確認したい場合は、たとえばここから取得できます



オペコードをキャッシュするという考え方は、理解と分析が容易になります。 Zendエンジンの作業とアーキテクチャを十分に理解する必要があり、最適化を行うことができる場所にすぐに気付くでしょう。



共有メモリモデル



ご存知のように、異なるオペレーティングシステムには、共有メモリの多くのモデルがあります。 現代のUnixシステムは、プロセスによるメモリの一般的な使用にいくつかのアプローチを使用していますが、最も一般的なものは次のとおりです。





OSがサポートしている場合、OPCacheは最初の3つを使用できます。 ini -setting opcache.preferred_memory_modelは、目的のモデルを明示的に設定します。 パラメーターをnullのままにすると、OPCacheはプラットフォームで実行されている最初のモデルを選択し、テーブルを順番に繰り返し処理します。



static const zend_shared_memory_handler_entry handler_table[] = { #ifdef USE_MMAP { "mmap", &zend_alloc_mmap_handlers }, #endif #ifdef USE_SHM { "shm", &zend_alloc_shm_handlers }, #endif #ifdef USE_SHM_OPEN { "posix", &zend_alloc_posix_handlers }, #endif #ifdef ZEND_WIN32 { "win32", &zend_alloc_win32_handlers }, #endif { NULL, NULL} };
      
      





デフォルトでは、 mmapを使用する必要があります。 これは優れたモデルであり、開発され、持続可能です。 ipcs



ipcrm



ように、System-V SHMモデルよりもシステム管理者にとって有益ではありませんが。



OPCacheが起動すると(つまり、PHPが起動すると)すぐに、共有メモリモデルをチェックし、1つの大きなセグメントを割り当てます。このセグメントは、部分的に分散されます。 この場合、セグメントは解放またはサイズ変更されなくなります。



つまり、PHPの起動時に、OPCacheは解放または断片化されていないメモリの1つの大きなセグメントを割り当てます。



opcache.memory_consumption INI設定を使用して、セグメントサイズをメガバイト単位で設定できます。 保存しないで、もっと聞いてください。 共有メモリの枯渇を許可しないでください。これが発生した場合、プロセスはブロックされます。 これについては以下で説明します。



必要に応じてセグメントサイズを設定します。PHPプロセス専用の運用サーバーは、PHPだけで数十ギガバイトのメモリを消費する可能性があることを忘れないでください。 そのため、セグメントに1 GB以上を割り当てることがよくありますが、すべて特定のニーズに依存します。 多数の依存関係などを備えた、フレームワークに基づいた最新のアプリケーションスタックを使用する場合は、少なくともギガバイトなしでは実行できません。



セグメントは、OPCacheによっていくつかのタスクに使用されます。





共有メモリセグメントには、オペコードだけでなく、OPCacheが機能するために必要なその他のものも含まれていることに注意してください。 したがって、必要なメモリ量を見積もり、必要なセグメントサイズを設定します。







オペコードキャッシング



キャッシングメカニズムの詳細を検討してください。



アイデアは、各ポインタのデータを共有メモリ(shm、共有メモリ)にコピーすることです。このデータは、要求ごとに変化しません。つまり、不変データです。 たくさんあります。 共有メモリから以前に使用されたスクリプトをロードした後、ポインタデータはプロセスの標準メモリに復元され、現在のリクエストに関連付けられます。 動作するPHPコンパイラは、Zend Memory Manager(Zend Memory Manager、ZMM)を使用して各ポインターを配置します。 このタイプのメモリはリクエストに関連付けられているため、ZMMは現在のリクエストが完了すると自動的にポインタを解放しようとします。 さらに、これらのポインターは現在の要求のヒープから配置されるため、他のPHPプロセスと共有できないプライベート拡張メモリのようなものであることがわかります。 したがって、OPCacheのタスクは、ポインターをこのプールに割り当てたままにしないで、割り当てられた共有メモリプールにコピーするように、PHPコンパイラによって返される各構造を調べることです。 そして、ここでコンパイル時間について話しています。 コンパイラによって投稿されたものはすべて不変と見なされます。 可変データは、実行時にZend仮想マシンによって作成されるため、Zendコンパイラーによって作成されたすべてを共有メモリに安全に保存できます。 例えば、関数とクラス、関数名へのポインター、OPArray関数へのポインター、クラス定数、宣言されたクラス変数の名前、そして最後に、それらのデフォルトの内容...多くのことがPHPコンパイラーによってメモリー内に作成されます。



このモデルは、ロックを確実に防止するために使用されます。 後ほど、ブロッキングのトピックに触れます。 本質的に、OPCacheは実行前にすべての作業をすぐに実行するため、OPCacheスクリプトの実行中に行うことはありません。 可変データは、ZMMを使用してプロセスのクラシックヒープで作成され、不変データは共有メモリから復元されます。



そのため、OPCacheはコンパイラーに接続し、後者がスクリプトのコンパイル中に埋めるべき構造を独自のコンパイラーに置き換えます。 次に、Zendエンジンのテーブルと内部構造を直接設定する代わりに、 persistent_script



構造を設定するようコンパイラーに強制します。



ここにあります:



 typedef struct _zend_persistent_script { ulong hash_value; char *full_path; /*      */ unsigned int full_path_len; zend_op_array main_op_array; HashTable function_table; HashTable class_table; long compiler_halt_offset; /*  __HALT_COMPILER  -1 */ int ping_auto_globals_mask; /*  autoglobal'   */ accel_time_t timestamp; /*    */ zend_bool corrupted; #if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO zend_uint early_binding; /*     */ #endif void *mem; /*  ,    */ size_t size; /*     */ /*  ,        ADLER32, *      struct */ struct zend_persistent_script_dynamic_members { time_t last_used; ulong hits; unsigned int memory_consumption; unsigned int checksum; time_t revalidate; } dynamic_members; } zend_persistent_script;
      
      





そのため、OPCacheは、関数ポインターを切り替えるだけで、コンパイラー構造をpersistent_scriptに置き換えます。



 new_persistent_script = create_persistent_script(); /*    op_array,      */ orig_active_op_array = CG(active_op_array); orig_function_table = CG(function_table); orig_class_table = CG(class_table); orig_user_error_handler = EG(user_error_handler); /*    */ CG(function_table) = &ZCG(function_table); EG(class_table) = CG(class_table) = &new_persistent_script->class_table; EG(user_error_handler) = NULL; zend_try { orig_compiler_options = CG(compiler_options); /*   */ CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY; CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES; CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING; CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION; op_array = *op_array_p = accelerator_orig_compile_file(file_handle, type TSRMLS_CC); /*  PHP- */ CG(compiler_options) = orig_compiler_options; } zend_catch { op_array = NULL; do_bailout = 1; CG(compiler_options) = orig_compiler_options; } zend_end_try(); /*   */ CG(active_op_array) = orig_active_op_array; CG(function_table) = orig_function_table; EG(class_table) = CG(class_table) = orig_class_table; EG(user_error_handler) = orig_user_error_handler;
      
      





ご覧のとおり、PHPコンパイラは完全に分離されており、通常のデータが含まれるテーブルからは切り離されています。 現在、 persistent_script



構造にデータを取り込みます。 次に、OPCacheはこれらの構造を調べて、要求ポインターを共有メモリへのポインターに置き換える必要があります。 OPCacheのニーズ:









コンパイラーには、最適化を無効にするいくつかのオプション(たとえば、 ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION



およびZEND_COMPILE_DELAYED_BINDING



)も与えられます。 これにより、OPCacheの作業が追加されます。 OPCacheはZendエンジンに接続することに注意してください。これはソースコードのパッチではありません。



これでpersitent_script



構造ができたので、その情報をキャッシュする必要があります。 PHPコンパイラは構造を埋めましたが、ZMMの助けを借りてエッジからメモリを割り当てました。現在のリクエストの最後に解放されます。 次に、このメモリを調べて、その内容を共有メモリセグメントにコピーして、収集された情報を複数のクエリに使用し、毎回再計算しないようにする必要があります。



プロセスの構造は次のとおりです。





そのため、OPCacheは共有メモリをインテリジェントに使用し、リリースや高密度化によって断片化することはありません。 スクリプトごとに、情報を保存するために必要な合計メモリの正確なサイズを計算し、そこにデータをコピーします。 メモリが解放されたり、OPCacheに返されたりすることはありません。 したがって、非常に効率的に使用され、断片化されません。 これにより、共有メモリのパフォーマンスが大幅に向上します。これは、(malloc / freeのように)解放できるメモリを管理しながら、保存および表示するリンクリストまたはBツリー(BTree)がないためです。 OPCacheは共有メモリセグメントにデータを保存し、スクリプトの関連性を確認したために関連性が失われると、バッファーは解放されませんが、「無駄」とマークされます。 失われたメモリの割合が最大に達すると、OPCacheが再起動します。 このモデルは、たとえばAPCとは大きく異なります。 その大きな利点は、共有メモリからのバッファが管理されない(解放されない、圧縮されないなど)ため、時間の経過とともにパフォーマンスが低下しないことです。 これらのメモリ管理操作はすべて純粋に技術的なものであり、機能を改善するのではなく、生産性を低下させます。 OPCacheは、PHP環境の実行を考慮して、可能な限り最高のパフォーマンスを提供するように設計されました。 共有メモリセグメントの「不可侵」は、非常に優れたプロセッサキャッシュヒット率(特にL1およびL2)も提供します。これは、OPCacheがメモリポインタをL1 / L2に揃えるためです。



スクリプトのキャッシュには、主にそのデータの正確なサイズの計算が含まれます。 計算アルゴリズムは次のとおりです。



 uint zend_accel_script_persist_calc(zend_persistent_script *new_persistent_script, char *key, unsigned int key_length TSRMLS_DC) { START_SIZE(); ADD_SIZE(zend_hash_persist_calc(&new_persistent_script->function_table, (int (*)(void* TSRMLS_DC)) zend_persist_op_array_calc, sizeof(zend_op_array) TSRMLS_CC)); ADD_SIZE(zend_accel_persist_class_table_calc(&new_persistent_script->class_table TSRMLS_CC)); ADD_SIZE(zend_persist_op_array_calc(&new_persistent_script->main_op_array TSRMLS_CC)); ADD_DUP_SIZE(key, key_length + 1); ADD_DUP_SIZE(new_persistent_script->full_path, new_persistent_script->full_path_len + 1); ADD_DUP_SIZE(new_persistent_script, sizeof(zend_persistent_script)); RETURN_SIZE(); }
      
      





繰り返します:キャッシュする必要があります:





反復アルゴリズムは、関数、クラス、およびOPArrayの詳細検索を実行します。すべてのポインターのデータをキャッシュします。 たとえば、PHP 5では、関数の場合、共有メモリ(shm)にコピーする必要があります。





PHP 7では、構造(ハッシュテーブルなど)の違いにより、リストはわずかに異なります。 私が言ったように、アイデアはすべてのポインタのデータを共有メモリにコピーすることです。 ディープコピーには構造の重複が含まれる場合があるため、OPCacheは変換テーブルを使用してポインターを格納します:ポインターが要求に関連付けられた通常のメモリから共有メモリにコピーされるたびに、古いポインターアドレスと新しいポインターアドレス間の接続がテーブルに書き込まれます。 コピーを処理するプロセスは、まず変換テーブルでこのデータが既にコピーされているかどうかを調べます。 コピーされた場合、古いポインターデータが使用されるため、重複はありません。



 void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC) { void **old_p, *retval; if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) { /* we already duplicated this pointer */ return *old_p; } retval = ZCG(mem);; ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size)); memcpy(retval, source, size); if (free_source) { interned_efree((char*)source); } zend_shared_alloc_register_xlat_entry(source, retval); return retval; }
      
      





ZCG(mem)



は、要素が追加されるといっぱいになる固定サイズの共有メモリセグメントです。 既に割り当てられているため、各コピーにメモリを割り当てる必要はありません(これにより全体のパフォーマンスが低下します)。セグメントを埋めるとき、ポインタのアドレスの境界がシフトします。



リクエストに関連付けられたヒープからポインタとデータを取得し、それが共有メモリにコピーされるスクリプトキャッシュアルゴリズムを調べました。 ロードアルゴリズムはまったく逆の処理を行います。共有メモリからpersistent_script



を取得し、すべての動的構造をスキャンして、共有ポインタをプロセスバウンドメモリにあるポインタにコピーします。 その後、スクリプトはZendエンジン(Zend Engine Executor)を使用して起動できるようになり、一般的なポインターのアドレスが埋め込まれなくなりました(あるスクリプトが別のスクリプトの構造を変更すると、深刻なバグが発生します)。 現在、ZendはOPCacheにだまされています。彼は、スクリプトの実行、ポインターの置換の前に何が起こったかに気付きませんでした。



通常のメモリから共有メモリへのコピー(スクリプトキャッシング)およびその逆(スクリプトの読み込み)のプロセスは最適化されており、パフォーマンスを向上させないコピーまたはハッシュ検索を多数実行する必要がある場合でも、毎回PHPコンパイラを実行するよりもはるかに高速です。



内部文字列ストレージの共有



インターンされた文字列は、PHP 5.4で導入された優れたメモリ最適化です。 これは論理的に見えます:PHPが文字列(char *)を検出すると、それを特別なバッファーに保存し、同じ文字列が検出されるたびにポインターを再び使用します。



次のように機能します。







すべてのポインターは同じ文字列インスタンスを使用します。 ただし、1つの問題があります。この内部行のバッファーはプロセスごとに個別に使用され、主にPHPコンパイラーによって制御されます。 つまり、PHP-FPMプールでは、各PHPワークフローがこのバッファーの独自のコピーを保存します。 このようなもの:







これは、特に多くのワークフローがある場合、およびコードで非常に大きな文字列を使用する場合(ヒント:PHPの説明コメントは文字列です)、大きなメモリ損失につながります。



OPCacheは、プール内のすべてのワークフロー間でこのバッファーを共有します。 このようなもの:







OPCacheは、共有メモリセグメントを使用して、これらの共有バッファをすべて格納します。 したがって、セグメントサイズを割り当てるときは、内部行ストレージの使用も考慮する必要があります。 opcache.interned_strings_bufferのINI構成を使用して、ストレージに共有メモリを使用する方法を構成できます。 もう一度思い出させてください。十分なメモリが割り当てられていることを確認してください。 これらの行スペースが不足した場合( opcache.interned_strings_bufferが低すぎる)、OPCacheは再起動しません。 結局、彼にはまだ十分な空き共有メモリがあり、文字列ストレージバッファのみがいっぱいで、リクエストの処理をブロックしません。 単に文字列を保存および共有することはできず、PHPワークフローのメモリを使用する文字列にもアクセスできなくなります。 パフォーマンスに影響を与えないように、このような状況を回避することが最善です。



ログを確認してください。このためにメモリが不足すると、OPCacheはこれについて警告します。



 if (ZCSG(interned_strings_top) + ZEND_MM_ALIGNED_SIZE(sizeof(Bucket) + nKeyLength) >= ZCSG(interned_strings_end)) { /*  ,     */ zend_accel_error(ACCEL_LOG_WARNING, "Interned string buffer overflow"); return arKey; }
      
      





このような行には、PHPコンパイラの操作中に遭遇するほぼすべての種類の行が含まれます。変数名、「php行」、関数名、クラス名...コメントは、今日「注釈」と呼ばれ、ほとんどの場合、巨大なサイズ。 それらはほとんどのバッファを占有するので、忘れないでください。



ロック機構



共有メモリについて話しているので、メモリブロッキングメカニズムについても話し合う必要があります。 本質はこれです: 共有メモリに書き込みたいすべてのPHPプロセスは、共有メモリに書き込みたい他のすべてのプロセスをブロックします 。 したがって、主な問題は読書ではなく、執筆に関連しています。 共有メモリから読み込むPHPプロセスは150個ありますが、同時に書き込むことができるのは1つだけです。 書き込み操作は読み取りをブロックせず、他の書き込み操作のみをブロックします。



そのため、OPCacheでは、キャッシュを大幅にウォームアップするまでデッドロックは発生しません 。 コードをデプロイした後、サーバーへのトラフィックを規制しない場合、スクリプトは集中的にコンパイルおよびキャッシュされ始めます。 また、共有メモリにキャッシュを書き込む操作は排他ロックの対象となるため、幸運な人がメモリへの書き込みを開始し、他の全員をブロックしたため、すべてのプロセスが起動します。 そして、ロックを解除すると、並んで待機している他のすべてのプロセスは、コンパイルしたばかりのファイルがすでに共有メモリに保存されていることを発見します。 そして、共有メモリからデータをロードするために、コンパイル結果を破棄し始めます。 これは許されないリソースの浪費です。



 /*   */ zend_shared_alloc_lock(TSRMLS_C); /* ,       ( ,     *  .      *  ) */ bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->full_path, new_persistent_script->full_path_len + 1); if (bucket) { zend_persistent_script *existing_persistent_script = (zend_persistent_script *)bucket->data; if (!existing_persistent_script->corrupted) { if (!ZCG(accel_directives).revalidate_path && (!ZCG(accel_directives).validate_timestamps || (new_persistent_script->timestamp == existing_persistent_script->timestamp))) { zend_accel_add_key(key, key_length, bucket TSRMLS_CC); } zend_shared_alloc_unlock(TSRMLS_C); return new_persistent_script; } }
      
      





サーバーを外部トラフィックから切断し、新しいコードを展開し、最も重いURLをcurlで調整して、curlリクエストが共有メモリを徐々に満たすようにする必要があります。 ほとんどのスクリプトが完了したら、トラフィックをサーバーに送信できます。共有メモリからのアクティブな読み取りが開始され、これによりロックが発生することはありません。 もちろん、まだコンパイルされていない小さなスクリプトが存在する場合がありますが、それらはほとんどないため、記録のブロッキングにはほとんど影響しません。



PHPファイルの書き込み中およびその使用中は避けてください。 その理由は同じです。実稼働サーバーのルートフォルダーに新しいファイルを書き込んでから使用する場合、つまり、数千のワークプロセスがコンパイルして共有メモリにキャッシュしようとする可能性があります。 そして、ロックがあります。 PHP- OPCache INI- opcache.blacklist-filename ( (glob pattern)).



, Unix — fcntl()



:



 void zend_shared_alloc_lock(TSRMLS_D) { while (1) { if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) { if (errno == EINTR) { continue; } zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno); } break; } ZCG(locked) = 1; zend_hash_init(&xlat_table, 100, NULL, NULL, 1); }
      
      





, : , , .



, : . .



OPCache



:





, OPCache ( , INI- opcache.revalidate_freq ) , . : , . PHP OPCache, PHP ( stat()



) : OPCache , «» stat()



.



(timestamp) opcache.validate_timestamps opcache.revalidate_freq , , OPCache «wasted». OPCache INI- opcache.max_wasted_percentage INI. . 他のオプションはありません。



 /*     */ memory_used = zend_accel_script_persist_calc(new_persistent_script, key, key_length TSRMLS_CC); /*    */ ZCG(mem) = zend_shared_alloc(memory_used); if (!ZCG(mem)) { zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM TSRMLS_CC); zend_shared_alloc_unlock(TSRMLS_C); return new_persistent_script; }
      
      









, , . «», OPCache . .



, . OPCache , . , : , . - . , , . .



.



, production-, ( , : OPCache persistent-, ). :





shell- 50 . , lsof



kill



. Unix ;-)



GUI- OPCache. opcache_get_status()



:







. (cache keys) .



OPCache , -, . - OPCache . ? .



OPCache . , realpath_cache , . , opcache.revalidate_path 1 realpath cache ( , , ).



, OPCache , realpath . , INI- opcache.revalidate_path 1. , OPCache unresolved . , , , OPCache - unresolved , ( ).



opcache.use_cwd 1, OPCache cwd



. , require_once "./foo.php";



。 , PHP ( ), opcache.use_cwd 1. , , opcache.revalidate_path . realpath- . www- , OPCache , opcache_reset()



.



- realpath- . opcache.use_cwd opcache.revalidate_path 1, . realpath OPCache, PHP , realpath_cache



.



, documentroot. , FPM- FastCGI, . , Lighttpd Nginx:





, , :





- , , , , . , documentroot- ( realpath-), . , production-. shell- 80.



, OPCache . : . OPCache , , : .



, .

OPCache opcache_get_status()



— , GUI — num_cached_keys . : INI- opcache.max_accelerated_files . , OPCache . , . . require_once



, OPCache . , include_once



, .



OPCache - persistent-, . - , . .



num_cached_scripts num_cached_keys , OPache. num_cached_keys . max_cached_keys , .



, , , OPache (INI- opcache.log_verbosity_level ). , , , OOM- (OutOfMemory): -.







 static void zend_accel_add_key(char *key, unsigned int key_length, zend_accel_hash_entry *bucket TSRMLS_DC) { if (!zend_accel_hash_find(&ZCSG(hash), key, key_length + 1)) { if (zend_accel_hash_is_full(&ZCSG(hash))) { zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); ZSMMG(memory_exhausted) = 1; zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH TSRMLS_CC); } else { char *new_key = zend_shared_alloc(key_length + 1); if (new_key) { memcpy(new_key, key, key_length + 1); if (zend_accel_hash_update(&ZCSG(hash), new_key, key_length + 1, 1, bucket)) { zend_accel_error(ACCEL_LOG_INFO, "Added key '%s'", new_key); } } else { zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM TSRMLS_CC); } } } }
      
      





:







PHP OPCache, opcache.memory_consumption (shm). ( opcache.interned_strings_buffer ). - persistent- . opcache.max_accelerated_files .



OPCache, . OPCache ( ). «», OPCache, ().



:







- persistent- , , OPCache ( ).



OPCache



, , Symfony, :





«». then opcache.max_wasted_percentage . FPM. FPM, , .



.



OPCache



はじめに



, . OPCache . , , Zend . , , . « ». , .



, OPArray, , . , , « PHP». , IS_VAR IS_CV , IS_CONSTIS_TMP_VAR . , runtime, .



OPCache IS_CONST . ( runtime); CGF- ( ) . . PHP: , . - , OPCache OPArray ( OPArray ), . PHP - — , : . , , , , . Java ++, «» - . PHP .



PHP . , , . - .



OPCache - . INI- opcache.optimization_level . :



 /* zend_optimizer.h */ #define ZEND_OPTIMIZER_PASS_1 (1<<0) /* CSE,  STRING */ #define ZEND_OPTIMIZER_PASS_2 (1<<1) /*     */ #define ZEND_OPTIMIZER_PASS_3 (1<<2) /* ++, +=,   */ #define ZEND_OPTIMIZER_PASS_4 (1<<3) /* INIT_FCALL_BY_NAME -> DO_FCALL */ #define ZEND_OPTIMIZER_PASS_5 (1<<4) /*    CFG */ #define ZEND_OPTIMIZER_PASS_6 (1<<5) #define ZEND_OPTIMIZER_PASS_7 (1<<6) #define ZEND_OPTIMIZER_PASS_8 (1<<7) #define ZEND_OPTIMIZER_PASS_9 (1<<8) /*  TMP VAR */ #define ZEND_OPTIMIZER_PASS_10 (1<<9) /*  NOP */ #define ZEND_OPTIMIZER_PASS_11 (1<<10) /*    */ #define ZEND_OPTIMIZER_PASS_12 (1<<11) /*    */ #define ZEND_OPTIMIZER_PASS_13 (1<<12) #define ZEND_OPTIMIZER_PASS_14 (1<<13) #define ZEND_OPTIMIZER_PASS_15 (1<<14) /*   */ #define ZEND_OPTIMIZER_ALL_PASSES 0xFFFFFFFF #define DEFAULT_OPTIMIZATION_LEVEL "0xFFFFBFFF"
      
      







, PHP 5 , , , OPCache. PHP 7 .



例:



 if (false) { echo "foo"; } else { echo "bar"; }
      
      





:







:







, if(false)



, Zend ZEND_ECHO



. , . , runtime.



, , ( ). IS_CONST IS_CV , :



 /*   ,    $a ? */ if ($a) { echo "foo"; } else { echo "bar"; }
      
      





, PHP 5 PHP 7:



 if (__DIR__ == '/tmp') { echo "foo"; } else { echo "bar"; }
      
      





PHP 7 __DIR__



, OPCache. OPCache. PHP 5.6 __DIR__



, . OPCache.



. PHP 5.6 PHP 7 OPCache, . , PHP 5.6 , PHP 7, PHP 5.6 , PHP 7 ( OPCache).



-



OPCache IS_TMP_VAR IS_CONST . , . - , . :





:



 if (function_exists('array_merge')) { echo 'yes'; }
      
      





, runtime:







:







, . , :



if function_exists('my_custom_function')) { }







, '__'. , PHP OPCache . :



 function my_custom_function() { } if function_exists('my_custom_function')) { }
      
      





, , ( ).



dirname()



( PHP 7):



 if (dirname(__FILE__) == '/tmp') { echo 'yo'; }
      
      





:







:







strlen()



PHP 7 . , . 例:



 if (strlen(dirname(__FILE__)) == 4) { echo "yes"; } else { echo "no"; }
      
      





:







:







, /, OPCache «» (, «» ).



(Transtyping)



OPCache IS_CONST , , . runtime:



 $a = 8; $c = $a + "42"; echo $c;
      
      





:







:







true- ZEND_ADD



: . ADD



. , runtime , , , . .



OPCache, :



 if (ZEND_OPTIMIZER_PASS_2 & OPTIMIZATION_LEVEL) { zend_op *opline; zend_op *end = op_array->opcodes + op_array->last; opline = op_array->opcodes; while (opline < end) { switch (opline->opcode) { case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: case ZEND_DIV: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (ZEND_OP1_LITERAL(opline).type == IS_STRING) { convert_scalar_to_number(&ZEND_OP1_LITERAL(opline) TSRMLS_CC); } } /* break missing *intentionally* -      op2 */ case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: if (opline->extended_value != 0) { /*       –   ! */ break; } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (ZEND_OP2_LITERAL(opline).type == IS_STRING) { convert_scalar_to_number(&ZEND_OP2_LITERAL(opline) TSRMLS_CC); } } break; /* ... ... */
      
      





, PHP 7. , PHP 7 OPCache ( ), , PHP 5.



IS_CONST , . PHP 5 , OPCache:



 $a = 4 + "33"; echo $a;
      
      





:







:







4 + 33



ZEND_ADD



, . runtime, . : PHP 7 , PHP 5 OPCache.





. , .



 $i = "foo"; $i = $i + 42; echo $i;
      
      





:







:







Zend VM ZEND_ASSIGN_ADD



ZEND_ADD



ZEND_ASSIGN



. $i+=3;



ZEND_ASSIGN_ADD



, ( , )



:



 $j = 4; $j++; echo $j;
      
      





:







:







OPCache ++$i



$i++



, . ZEND_POST_INC



— , , , , ZEND_PRE_INC



: , ( ). , ZEND_POST_INC



, , ZEND_FREE



. OPCache ZEND_PRE_INC



ZEND_FREE



: runtime.





PHP-? , . , , . :



 const FOO = "bar"; echo FOO;
      
      









:







. , , , runtime .



define()



const



, :



 define('FOO', 'bar'); echo FOO;
      
      





:







:







define()



, , runtime, ( define()



). これは非常に悪いです。 const



DECLARE_CONST



. Zend .



(Multiple jump target resolution)



, . ( ). , , . — , . PHP- . if



, switch



, while



, try



, foreach



, ?



: — . true, , — .



, . landing- , landing-. , «».



例:



 if ($a) { goto a; } else { echo "no"; } a: echo "a";
      
      





:







( ): « $a 0, 3, «no». 4, «a»».



- « 3, 4». « 4»? :







« $a , 2 «a», «no»». , ? , . , while



if



, goto



, switch



, try-catches



, .. OPArray . , . ( ) . runtime.



おわりに



. , « » (early returns). try-catch switch-break. PHP, .



, , . OPCache , , PHP , , … , , . .



OPCache , PHP. PHP 7, . PHP 7 ( ) PHP 5 ( PHP 5 ).



, , . , . , , , . , , OPCache. : , . ( runtime).



終わり



, OPCache - . , , . OPCache PHP, . PHP- , , . , — PHP-FPM SAPI.



All Articles