この記事は、PHP7 のアップデートとパフォーマンスの最適化についての紹介 (写真とテキスト) を提供します。一定の参考価値があります。必要な友人は参照してください。お役に立てれば幸いです。助けてくれた。
PHP7 のイノベーションとパフォーマンスの最適化
私は幸運にも 2015 PHP Technology Summit (PHPCON) に参加し、Niao 兄弟 (Hui Xinchen) から新機能について話を聞くことができました。 PHP7 のパフォーマンスの最適化を共有することは刺激的です。ニアオ兄弟は中国で最も権威のある PHP 専門家であり、彼の共有には多くの非常に貴重な内容が含まれています。私は共有された PPT を編集し、関連情報をこの解説的な技術記事にまとめて、PHP 開発を行う学生に役立つことを願っています。ヘルプ。
PHP は 20 年の歴史を持ち、現在まで PHP7 は RC 版がリリースされていますが、正式版は 2015 年 11 月頃にリリースされると言われています。 PHP7 は、前シリーズの PHP5.* に比べて大規模な革新と言え、特にパフォーマンスの面で飛躍的な向上を実現しました。
PHP は世界中で広く使用されている Web 開発言語であり、PHP7 の革新はこれらの Web サービスにさらに大きな変化をもたらすことは間違いありません。これは Niao Ge の PPT のグラフです (Web サイトの 82% が開発言語として PHP を使用しています):
(注: Web サイトは開発言語として複数の言語を使用できます)言語)
(注: この記事には、Niao Ge の PPT からのスクリーンショットが多数含まれています。写真の著作権は Niao Ge に属します)
まず 2 つのエキサイティングなパフォーマンスを見てみましょう テスト結果の図:
ベンチマーク比較 (PPT からの画像):
PHP7 のパフォーマンス テスト結果、パフォーマンス ストレス テストの結果、消費時間は 2.991 から 1.186 に減少し、大幅に短縮されました。 60%の低下。
WordPress の QPS ストレス テスト (PPT からの画像):
WordPress プロジェクトでは、PHP5.6 と比較して、PHP7 では QPS が 2.77 増加しました。回。
パフォーマンス テスト結果の興味深い比較を読んだ後、本題に入りましょう。 PHP7 には多くの新機能がありますが、主な変更点に焦点を当てます。
1. 新機能と変更点
1. スカラー型宣言とスカラー型宣言
PHP 言語 非常に重要な機能は「弱い型付け」です。 , そのため、PHP プログラムは非常に簡単に作成でき、初心者でもすぐに PHP に触れることができますが、いくつかの論争も伴います。変数の型定義のサポートは革新的な変更と言え、PHP はオプションの方法で型定義をサポートし始めます。さらに、switch 命令declare(strict_type=1); も導入されており、この命令がオンになると、現在のファイルの下のプログラムが関数パラメータの転送型と戻り値の型に厳密に従うように強制されます。
たとえば、add 関数と型定義は次のように記述できます。
必須の型切り替え命令と組み合わせると、次のように記述できます。次のように:
strict_type がオンになっていない場合、PHP は必要な型への変換を支援します。これをオンにすると、PHP は型変換を実行しなくなります。 、型の不一致がスローされます。これは、「厳密に型指定された」言語を好む学生にとって朗報です。
より詳細な紹介:
PHP7 スカラー型宣言 RFC [翻訳]
2. より多くのエラーがキャッチ可能になる例外
PHP7 はグローバルなスロー可能なインターフェイスを実装しています。オリジナルの Exception と一部の Error は、このインターフェイス (インターフェース) を実装し、例外の継承構造をインターフェースの形式で定義します。その結果、PHP7 ではより多くのエラーがキャッチ可能な例外となって開発者に返され、キャッチされなかった場合はエラー、キャッチされた場合はプログラム内で処理できる例外となるようになりました。これらのキャッチ可能なエラーは、通常、存在しない関数など、プログラムに致命的な害を及ぼさないエラーです。 PHP7 は開発者の処理をさらに容易にし、開発者がプログラムをより詳細に制御できるようにします。デフォルトでは、エラーによりプログラムが直接中断されますが、PHP7 はエラーをキャプチャして処理する機能を提供し、プログラムの実行を継続できるため、プログラマはより柔軟な選択肢を得ることができます。
例えば、存在するかどうか不明な関数を実行する場合、PHP5互換のメソッドでは関数を呼び出す前に判定 function_exist を付加しますが、PHP7では例外をキャッチするハンドリングメソッドがサポートされています。 。
下の図の例 (スクリーンショットは PPT からのもの):
3. AST (抽象構文ツリー、抽象構文ツリー)
AST PHP コンパイル プロセスのミドルウェアとして、インタープリターからオペコードを直接吐き出す元の方法を置き換え、インタープリター (パーサー) とコンパイラー (コンパイラー) を分離します。これにより、一部のハック コードが削減され、実装が理解しやすくなります。 .そしてメンテナンス可能。
PHP5:
PHP7:
AST 情報の詳細:
https://wiki.php.net/rfc/abstract_syntax_tree
4. ネイティブ TLS (ネイティブ スレッド ローカル ストレージ、ネイティブ スレッド ローカル ストレージ)
マルチスレッド モードの PHP (たとえば、 Web サーバー Apache のワーカー モードとイベント モードはマルチスレッドです) であり、「スレッド セーフ」(TS、スレッド セーフ) の問題を解決する必要があります。スレッドはプロセスのメモリ空間を共有するため、各スレッド自体がいくつかのスレッドを渡す必要があります。他のスレッドとの相互汚染を避けるために、自分のプライベート データを保存するプライベート スペースを構築します。 PHP5 で採用されている方法は、大規模なグローバル配列を維持し、各スレッドに独立した記憶領域を割り当てることであり、スレッドは独自のキー値を通じてこのグローバル データ グループにアクセスします。
この一意のキー値は、PHP5 でグローバル変数を使用する必要があるすべての関数に渡す必要があります。PHP7 では、この渡し方法は不親切で、いくつかの問題があると考えられています。したがって、このキー値を保存するには、スレッド固有のグローバル変数を使用してみてください。
関連するネイティブ TLS の問題:
https://wiki.php.net/rfc/native-tls
5. その他の新機能
PHP7 には多くの新機能や変更点があるため、ここでは詳しく説明しません。
(1) Int64 サポート。さまざまなプラットフォームで整数の長さを統一し、文字列とファイルのアップロードの両方で 2GB を超えるサイズをサポートします。
(2) 統一された変数構文。
(3) 一貫した foreach 動作
(4) 新しい演算子 <=>、??
(5) Unicode 文字形式のサポート (\u{xxxxx} )
(6) 匿名クラスのサポート (匿名クラス)
… …
2. 飛躍的なパフォーマンスのブレークスルー: 全速力で前進
1. JIT とパフォーマンス
Just In Time (ジャストインタイム コンパイル) はソフトウェア最適化テクノロジであり、実行時にバイトコードがマシン コードにコンパイルされることを意味します。直感的に考えると、マシンコードはコンピュータが直接認識して実行できるため、Zend よりもオペコードを 1 つずつ読み取って実行する方が効率的であると考えられます。その中でも、HHVM (HipHop Virtual Machine、HHVM は Facebook のオープンソース PHP 仮想マシンです) は JIT を使用しており、PHP のパフォーマンス テストが桁違いに向上し、衝撃的なテスト結果が公開されています。これは、JIT が A であると直感的に思わせるものでもあります。石を金に変える強力な技術。
実際、2013 年に、Niao 兄弟と Dmitry (PHP 言語コア開発者の 1 人) は、PHP5.5 バージョン (未リリース) で JIT を試みたことがありました。 PHP5.5 の本来の実行プロセスは、字句解析および構文解析を通じて PHP コードをオペコード バイトコードにコンパイルし (形式はアセンブリに似ています)、その後、Zend エンジンがこれらのオペコード命令を読み取り、1 つずつ解析して実行します。
そして、オペコードリンクの後に型推論 (TypeInf) を導入し、JIT を通じて ByteCode を生成して実行しました。
その結果、ベンチマーク(テストプログラム)では、JIT導入後はPHP5.5と比較して8倍のパフォーマンス向上という素晴らしい結果が得られました。しかし、この最適化を実際のプロジェクト WordPress (オープンソース ブログ プロジェクト) に導入したところ、パフォーマンスの向上はほとんど見られず、不可解なテスト結果が得られました。
そこで、彼らは Linux でプロファイル タイプ ツールを使用して、プログラム実行の CPU 時間消費を分析しました。
WordPress を 100 回実行したときの CPU 消費量の分布 (PPT からのスクリーンショット):
注:
CPU 時間の 21% はメモリ管理に費やされます。
CPU 時間の 12% は、主に PHP 配列の追加、削除、変更、チェックなどのハッシュ テーブル操作に費やされます。
CPU 時間の 30% は、strlen などの組み込み関数に費やされます。
CPU 時間の 25% が VM (Zend Engine) に費やされます。
分析の結果、次の 2 つの結論が得られました。
(1) JIT によって生成された ByteCode が大きすぎると、CPU キャッシュのヒット率が低下します (CPU キャッシュ ミス)
PHP5.5 コードでは、明確な型定義がないため、型推論のみに依存できます。可能な限り推論できる変数の型を定義し、型推論と組み合わせて、その型以外の分岐コードを削除し、直接実行可能な機械語コードを生成します。ただし、型推論ではすべての型を推論できるわけではなく、WordPress では推論できる型情報は 30% 以下に限られており、削減できる分岐コードも限られています。その結果、JIT 後にマシン コードが直接生成され、生成された ByteCode が大きすぎるため、最終的に CPU キャッシュ ヒット (CPU キャッシュ ミス) が大幅に減少します。
CPU キャッシュ ヒットとは、CPU が命令を読み取って実行するときに、必要なデータが CPU の 1 次キャッシュ (L1) に読み込めない場合、下方への検索を継続する必要があることを意味します。2 次キャッシュ ( L2) と 3 次キャッシュ (L3) を使用すると、最終的にはメモリ領域で必要な命令データを見つけようとし、メモリと CPU キャッシュの読み取り時間の差は 100 倍に達することがあります。したがって、ByteCode が大きすぎて実行される命令の数が多すぎる場合、多値キャッシュではそれほど多くのデータを収容できず、一部の命令をメモリ領域に格納する必要があります。
CPU のすべてのレベルでのキャッシュのサイズも制限されており、次の図は Intel i7 920 の構成情報です:
したがって、CPU キャッシュ ヒット率の低下は、消費時間の大幅な増加を引き起こす一方、JIT によるパフォーマンスの向上も相殺されます。
JIT により VM のオーバーヘッドが削減されると同時に、命令の最適化によりメモリ割り当ての数が削減されるため、間接的にメモリ管理の開発を削減できます。ただし、実際の WordPress プロジェクトの場合、VM に費やされる CPU 時間はわずか 25% であり、主な問題やボトルネックは実際には VM にありません。したがって、JIT 最適化プランは、このバージョンの PHP7 機能には含まれていませんでした。ただし、今後のバージョンで実装される可能性は高いので、期待しておきましょう。
(2) JIT パフォーマンスの改善効果は、プロジェクトの実際のボトルネックに依存します。
コード量が比較的少なく、最終的に生成されるバイトコードが少ないため、ベンチマークでは JIT が大幅に改善されました。も比較的小さく、主なオーバーヘッドは VM にあります。ただし、実際の WordPress プロジェクトでは、WordPress のコード量がベンチマークよりもはるかに大きいため、明らかなパフォーマンスの向上はありません。JIT は VM のオーバーヘッドを削減しますが、CPU キャッシュ ヒットと余分なメモリの減少を引き起こします。 ByteCode が大きすぎるため、オーバーヘッドが発生し、最終的には改善されません。
プロジェクトの種類が異なると、CPU オーバーヘッド率も異なり、結果も異なります。実際のプロジェクトを使用しないパフォーマンス テストは、あまり代表的ではありません。
2. Zval の変更点
#実際、PHP のさまざまなタイプの変数の実際の記憶媒体は Zval であり、その許容範囲と耐性が特徴です。本質的には、C言語で実装された構造体(struct)です。 PHP を書く学生にとっては、大まかに配列のようなものとして理解できます。
PHP5 の Zval、メモリは 24 バイトを占有します (PPT からのスクリーンショット):
PHP7 の Zval、メモリは 16 バイトを占有します (PPT からのスクリーンショット):
Zval は 24 バイトから 16 バイトに減少しました。なぜ減少しましたか? ここでは、C に詳しくない人を支援するために、C 言語の基礎を少し追加する必要があります。生徒たちは理解しています。 struct と Union (union) の間には若干の違いがあり、Struct の各メンバー変数は独立したメモリ空間を占有しますが、union のメンバー変数はメモリ空間を共有します (つまり、メンバー変数の 1 つが変更されると、パブリックスペースは変更後、他のメンバー変数の記録はありません)。したがって、メンバー変数がより多くなったように見えますが、実際に占有されるメモリ領域は減少しています。
さらに、大幅に変更された機能が他にもあり、一部の単純な型では参照が使用されなくなりました。
Zval 構造図 (PPT より):
画像の
Zval は 2 つの 64 ビット (1 バイト = 8 ビット、ビットは「ビット」) で構成されています。変数の型が long または bealoon で、長さが 64 ビットを超えない場合は、値に直接格納されます。 、以下の引用はありません。変数の型が 64 ビットを超える配列、オブジェクト、文字列などの場合、格納される値は実際の記憶構造アドレスを指すポインタになります。
単純な変数型の場合、Zval ストレージは非常にシンプルかつ効率的になります。
参照が必要ない型: NULL、Boolean、Long、Double
参照が必要な型: String、Array、Object、Resource、Reference
3. 内部型zend_string
Zend_string は実際に文字列を格納する構造体で、実際の内容は val (char、文字型) に格納され、val は長さ 1 の char 配列 (メンバ変数の占有に便利) です。
構造体の最後のメンバー変数は、char* の代わりに char 配列を使用します。ここでは、CPU キャッシュ ミスを減らすことができる小さな最適化トリックを紹介します。
char 配列を使用する場合、malloc が上記の構造体のメモリに適用されるとき、同じ領域に適用されます。通常、長さは実際の char ストレージ領域の sizeof(_zend_string) になります。ただし、char* を使用する場合、この場所に格納されるのは単なるポインタであり、実際の格納場所は別の独立したメモリ領域にあります。
char[1] と char* を使用したメモリ割り当ての比較:
論理実装の観点から見ると、実際には 2 つの間に大きな違いはありません. 効果は非常に似ています。実際、これらのメモリ ブロックが CPU にロードされると、見た目は大きく異なります。前者は連続して割り当てられた同じメモリであるため、通常、CPU がそれを読み取るときに一緒に取得できます (同じレベルのキャッシュにあるため)。後者は、2 つのメモリからのデータが含まれているため、CPU が最初のメモリを読み取るときに、2 番目のメモリのデータが同じレベルのキャッシュにない可能性が非常に高いため、CPU は L2 (2 次キャッシュ) の下を検索する必要があります。 Even to 目的の 2 番目のメモリデータがメモリ領域に見つかりました。これにより CPU キャッシュ ミスが発生し、両者の時間の差は最大 100 倍になる可能性があります。
さらに、文字列をコピーするとき、参照割り当てを使用すると、zend_string はメモリのコピーを回避できます。
6. PHP 配列の変更 (HashTable と Zend Array)
PHP プログラムを作成するプロセスで最も頻繁に使用される型は配列であり、PHP5 の配列は HashTable を使用して実装されます。ざっくりまとめると、二重リンクリストをサポートする HashTable で、配列キーを介して要素にアクセスするためのハッシュ マッピングをサポートするだけでなく、foreach を介して二重リンク リストにアクセスすることで配列要素を横断することもできます。
PHP5 HashTable (PPT からのスクリーンショット):
この図は、さまざまなポインターが飛び回っており、非常に複雑に見えます。キーを渡すときコンテンツにアクセスするとき要素を値で取得するため、必要なコンテンツを見つけるために 3 回のポインター ジャンプが必要になる場合があります。最も重要な点は、これらの配列要素の格納が異なるメモリ領域に分散されていることです。同様に、CPU が読み取りを行う場合、同じレベルのキャッシュにない可能性が高いため、CPU は下位レベルのキャッシュ、さらにはメモリ領域を検索する必要があり、これにより CPU キャッシュ ヒットが減少します。消費量が増加する時間。
PHP7 の Zend 配列 (PPT からのスクリーンショット):
新しいバージョンの配列構造は非常にシンプルで目を引きます。最大の特徴は、配列要素全体とハッシュマッピングテーブルがすべて接続され、同じメモリ上に割り当てられていることです。単純型の整数配列を走査している場合、配列要素 (バケット) 自体が同じメモリ内に継続的に割り当てられ、配列要素の zval が整数要素を内部的に格納するため、効率が非常に速くなります。ポインタ外部リンクでもあり、すべてのデータは現在のメモリ領域に保存されます。もちろん、最も重要なことは、CPU キャッシュ ミス (CPU キャッシュ ヒット率の低下) を回避できることです。
Zend 配列の変更:
(1) 配列の値のデフォルトは zval です。
(2) HashTable のサイズは 72 バイトから 56 バイトに減少し、22% 削減されました。
(3) バケットのサイズは 72 バイトから 32 バイトに減少し、50% 削減されました。
(4) 配列要素のバケットのメモリ空間はまとめて割り当てられます。
(5) 配列要素 (Bucket.key) のキーは zend_string を指します。
(6) 配列要素の値がバケットに埋め込まれます。
(7) CPU キャッシュミスを削減します。
7. 関数呼び出し規約
PHP7 では関数呼び出しの仕組みが改良され、パラメーターの転送処理を最適化することで一部の命令が削減され、実行効率が向上しました。
PHP5 の関数呼び出しメカニズム (PPT からのスクリーンショット):
図では、vm スタック内の命令 send_val と recv パラメーターは同じです (PHP7)これら 2 つの重複を減らすことで、関数呼び出しメカニズムの根本的な最適化を実現します。
PHP7 の関数呼び出しメカニズム (PPT からのスクリーンショット):
8. マクロ定義とインライン関数 ( inline ) を通じて、コンパイラは作業の一部を事前に完了させます。
C 言語のマクロ定義は前処理段階 (コンパイル段階) で実行され、作業の一部は事前に完了しており、プログラムの実行時にメモリを割り当て、同様の関数を実装することもできますが、関数呼び出しのスタックのプッシュとポップのオーバーヘッドがなければ、効率が高くなります。インライン関数も同様で、前処理の段階でプログラム中の関数を関数本体に置き換えますが、ここで実際に実行するプログラムを実行する際には関数呼び出しのオーバーヘッドは発生しません。
PHP7 はこの分野で多くの最適化を行い、実行フェーズで実行する必要がある多くの作業をコンパイルフェーズに組み込みました。例えば、パラメータの型判定(Parameters Parsing)はすべて固定の文字定数であるため、コンパイル段階で完了でき、その後の実行効率が向上します。
たとえば、下図では、左の記述方法から右のマクロの記述方法まで、渡されるパラメータの型の扱い方が最適化されています。
3. 概要
ニアオ兄弟の PPT は、WordPress が PHP5 で 100 回実行されるという一連の比較データをリリースしました。 .6 これにより、CPU 命令の実行が 70 億回発生するのに対し、PHP7 では 25 億回で、64.2% 削減されるという衝撃的なデータです。
ブラザー・バードの共有全体の中で、私にとって最も深い視点は次のとおりです。細部に注意を払い、多くの小さな最適化を行い、少しずつ継続的に蓄積し、何かを積み上げ、最終的には驚くべき結果に収束する. .おそらく9人で山を築くのも同じ理由だと思います。
PHP7 が飛躍的なパフォーマンスの向上を達成したことは疑いの余地がありません。これらの結果を PHP の Web システムに適用できれば、おそらくより少ないマシンでより多くのリクエスト量をサポートできるようになるでしょう。 PHP7 の正式版のリリースには、尽きない期待が満ちています。
以上がPHP7 のアップデートとパフォーマンスの最適化の概要 (写真とテキスト)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。