序文
どの言語においても、関数は最も基本的な構成要素です。 PHPの関数にはどのような特徴があるのでしょうか?関数呼び出しはどのように実装されますか? php 関数のパフォーマンスはどうですか?使用方法に関する提案はありますか?この記事では、実装を理解しながら PHP プログラムをより適切に作成できるように、原理を分析し、実際のパフォーマンス テストと組み合わせることで、これらの質問に答えていきます。同時に、いくつかの一般的な PHP 関数が紹介されます。
PHP 関数の分類
PHP では、関数を横に分けるとユーザー関数 (組み込み関数) の 2 つに分類されます。そして内部機能。前者はプログラム内でユーザーがカスタマイズした一部の関数やメソッドであり、後者はPHP自体が提供する各種ライブラリ関数(sprintfやarray_pushなど)です。ユーザーは、後で紹介する拡張メソッドを使用してライブラリ関数を作成することもできます。ユーザー関数はさらにファンクション(関数)とメソッド(クラスメソッド)に分かれますが、今回はこの3つの関数をそれぞれ分析・テストしていきます。
推奨チュートリアル: PHP ビデオ チュートリアル
##php 関数の実装
PHP 関数は最終的にどのように実行されるのでしょうか?そのプロセスはどのようなものですか? この質問に答えるために、まず PHP コードを実行するプロセスを見てみましょう。 図からわかるように、PHP は典型的な動的言語実行プロセスを実装します。コードの一部を取得した後、字句解析や構文解析などの段階を経た後です。 、ソース コード プログラムは命令 (オペコード) に変換され、ZEND 仮想マシンはこれらの命令を順番に実行して操作を完了します。 PHP 自体は C で実装されているため、最終的に呼び出される関数はすべて C の関数となり、PHP は C で開発されたソフトウェアとみなすことができます。 上記の説明から、PHP での関数の実行も呼び出し用のオペコードに変換されることがわかります。各関数呼び出しは実際に 1 つ以上の命令を実行します。 各関数について、zend は次のデータ構造で記述されます。typedef union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; } common; zend_op_array op_array; zend_internal_function internal_function; } zend_function; typedef struct _zend_function_state { HashTable *function_symbol_table; zend_function *function; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; } zend_function_state;
組み込み関数
組み込み関数は本質的に実際の C 関数です。組み込み関数ごとに、PHP はそれを展開します。これは、一般的な sprintf のような zif_xxxx という名前の関数になり、最下層の zif_sprintf に対応します。 Zend の実行中に組み込み関数が見つかると、転送操作が実行されます。 Zend は、パラメータの取得、配列操作、メモリ割り当てなどを含む、呼び出し用の一連の API を提供します。組み込み関数のパラメータは zend_parse_parameters メソッドを通じて取得されます。配列や文字列などのパラメータについては、zend は浅いコピーを実装しているため、この効率は非常に高くなります。 PHP 組み込み関数の場合、転送呼び出しが追加されるだけで、その効率は対応する C 関数の効率とほぼ同じであると言えます。 組み込み関数は so を通じて PHP に動的にロードされますが、ユーザーは必要に応じて対応する so を記述することもできます (これは、私たちがよく拡張機能と呼ぶものです)。 ZEND は、拡張機能で使用するための一連の API を提供します#ユーザー関数組み込み関数と比較して、PHP によって実装されたユーザー定義関数には、実行プロセスと実装原則がまったく異なります。上で述べたように、PHP コードは実行のためにオペコードに変換され、ユーザー関数も例外ではありません。実際、各関数はオペコードのセットに対応し、この命令セットは zend_function に保存されます。したがって、ユーザー関数の呼び出しは、最終的に一連のオペコードの実行に対応します。
ローカル変数を保存して再帰を実装する 関数の再帰はスタックを通じて完了することがわかっています。 php では、これを実現するために同様の方法が使用されます。 Zend は、アクティブ シンボル テーブル (active_sym_table) を各 PHP 関数に割り当てて、現在の関数内のすべてのローカル変数のステータスを記録します。すべてのシンボル テーブルはスタック形式で保持され、関数が呼び出されるたびに、新しいシンボル テーブルが割り当てられ、スタックにプッシュされます。呼び出しが終了すると、現在のシンボル テーブルがスタックからポップされます。これにより、状態の保存と再帰が可能になります。 スタックのメンテナンスのために、zend はここで最適化を行いました。スタックをシミュレートするために、長さ N の静的配列を事前に割り当てます。静的配列を通じて動的データ構造をシミュレートするこの方法は、独自のプログラムでよく使用されます。この方法により、各呼び出しによって発生するメモリ割り当てが回避されます。 ZEND は、関数呼び出しの終了時に現在のスタックの最上位にあるシンボル テーブル データをクリーンアップするだけです。 静的配列の長さが N であるため、関数呼び出しレベルが N を超えると、プログラムはスタック オーバーフローを引き起こしません。この場合、zend はシンボル テーブルを割り当てて破棄するため、パフォーマンスが大幅に低下します。劣化。 zend では、N の現在の値は 32 です。したがって、PHP プログラムを作成するときは、関数呼び出しレベルが 32 を超えないようにすることが最善です。もちろんWebアプリケーションであれば関数呼び出しレベル自体は深くても構いません。 パラメータの転送 zend_parse_params を呼び出してパラメータを取得する組み込み関数とは異なり、ユーザー関数でのパラメータの取得は次の手順で完了します。説明書。関数に含まれるパラメーターの数は、関数に含まれる命令の数に対応します。実装に特有の、通常の変数の代入です。 上記の分析から、組み込み関数と比較した場合、スタック テーブルが独自に維持され、各命令の実行も C 関数であるため、ユーザー関数のパフォーマンスがわかることがわかります。具体的な比較分析については後述します。したがって、関数に対応する PHP 組み込み関数がある場合は、その関数を自分で書き直して実装しないようにしてください。 クラスメソッド クラスメソッドの実行原理はユーザー関数と同じで、これもオペコードに変換されて呼び出されます。順次。クラスの実装は、データ構造 zend_class_entry を使用して zend によって実装されます。この構造には、クラスに関連するいくつかの基本情報が格納されます。このエントリは、PHP のコンパイル時に処理されます。 zend_function の common には、現在のメソッドに対応するクラスの zend_class_entry を指す、scope というメンバーがあります。 PHP におけるオブジェクト指向の実装については、ここでは詳しく説明しませんが、今後、特別な記事を書いて PHP におけるオブジェクト指向の実装原理について詳しく説明する予定です。関数に関する限り、メソッドの実装原理は関数の実装原理とまったく同じであり、そのパフォーマンスは理論的には同様です。詳細なパフォーマンスの比較は後で行います。 関数名の長さがパフォーマンスに与える影響 テスト方法 名前の長さ 1 の場合、2、4、8、および 16 個の関数をテストし、1 秒あたりに実行できる回数を比較し、関数名の長さがパフォーマンスに与える影響を確認します。 テスト結果は次のとおりです。以下 結果分析 # 図からわかるように、関数名の長さはまだ残っています。パフォーマンスに一定の影響を与えます。長さ 1 の関数と長さ 16 の空の関数呼び出しでは、パフォーマンスに 1 倍の違いがあります。ソースコードを分析することで原因を見つけることは難しくありませんが、前述のように、関数が呼び出されるとき、zend はまずグローバルな function_table (ハッシュ テーブル) 内の関数名によって関連情報を照会します。必然的に、名前が長ければ長いほど、クエリにかかる時間も長くなります。したがって、実際にプログラムを作成する際には、複数回呼び出される関数名はあまり長くしないことをお勧めします。 関数名の長さはパフォーマンスに一定の影響を与えますが、どの程度の大きさにするかは重要です。具体的には?この問題は、実際の状況に基づいて検討する必要がありますが、機能自体が比較的複雑であれば、全体のパフォーマンスに大きな影響を与えることはありません。 提案の 1 つは、何度も呼び出され、比較的単純な機能を持つ関数には簡潔で簡潔な名前を付けることです。 #関数の数がパフォーマンスに及ぼす影響 テスト方法 テスト結果は次のとおりです 結果分析 実装原理の分析から、いくつかの実装間の唯一の違いは、機能取得部分です。前述したように、すべての関数はハッシュ テーブルに配置され、検索効率は数値が異なっても O(1) に近いため、パフォーマンスの差は大きくありません。 さまざまなタイプの関数呼び出しの使用 テスト メソッド ユーザー関数、クラスの選択メソッドには、静的メソッドと組み込み関数の 1 種類があり、関数自体は何もせずに直接戻ります。主に空の関数呼び出しの消費をテストします。テスト結果は 1 秒あたりの実行数です。 テスト中に他の影響を取り除くため、すべての関数名は同じ長さです。 テスト結果は次のとおりです 結果分析 テスト結果から、ユーザー自身が作成した PHP 関数については、どのタイプでも効率はほぼ同じで約280w/sです。予想通り、エアコンでも内蔵機能の効率ははるかに高く、780w/sと従来の3倍に達します。組み込み関数呼び出しのオーバーヘッドは、ユーザー関数のオーバーヘッドよりもはるかに低いことがわかります。前述の原理分析から、主なギャップはシンボル テーブルの初期化やユーザー関数の呼び出し時のパラメーターの受信などの操作にあることがわかります。 組み込み関数とユーザー関数のパフォーマンス比較 テスト方法 関数とユーザー関数 パフォーマンスを比較するために、ここではよく使用される関数をいくつか選択し、PHP 関数を使用して同じ関数を実装してパフォーマンスを比較します。 テストでは、文字列インターセプト (substr)、10 進数から 2 進数への変換 (decbin)、最小値 (min) の関数を、文字列、数学、配列の中から代表的なものを選択して比較しました。配列内のすべてのキー (array_keys)。 テスト結果は以下のとおりです。 結果分析 テスト結果からわかるように、予想どおり、組み込み関数の全体的なパフォーマンスは、通常のユーザー関数のパフォーマンスよりもはるかに高いことがわかりました。特に文字列操作を含む関数の場合、そのギャップは 1 桁に達します。したがって、関数を使用する場合の 1 つの原則は、特定の関数に対応する組み込み関数がある場合は、PHP 関数を自分で作成する代わりに、それを使用するようにすることです。 多数の文字列操作を伴う一部の関数では、パフォーマンスを向上させるために、拡張機能を使用して実装することを検討できます。たとえば、一般的なリッチ テキスト フィルタリングなどです。 C関数との性能比較 テスト方法 文字列演算と算術演算の関数を3つずつ選んで比較します PHPでは拡張機能の達成を使用しています。 3 つの関数は、単純な 1 回限りの算術演算、文字列比較、および複数の算術演算です。 2 種類の関数に加えて、空調のオーバーヘッドを取り除いた後のパフォーマンスもテストします。一方で、2 つの関数 (C と PHP の組み込み関数) のパフォーマンスの違いを比較します。機能消費量 テストポイントは10万回のオペレーションを実行したときの消費時間です テスト結果は以下の通りです以下に示す 結果分析 組み込み関数と C 関数のコストの差は、次のとおりです。 php 関数の空調の影響を取り除く 関数がますます複雑になるにつれて、両者のパフォーマンスは同じに近づきます。これは、前述の関数実装分析から簡単に証明できますが、結局のところ、組み込み関数は C で実装されています。 関数が複雑になればなるほど、C と PHP のパフォーマンスの差は小さくなります。 C と比較すると、PHP 関数呼び出しのオーバーヘッドははるかに高く、単純な関数のパフォーマンスにはまだ限界があります。確かなインパクト。したがって、PHP の関数はあまり深くネストしたりカプセル化したりしないでください。 疑似関数とそのパフォーマンス PHP には標準関数の使用法であるいくつかの関数がありますが、その基礎となる実装は完全にこれらの関数は、実際の関数呼び出しとは異なり、上記の 3 つの関数のいずれにも属さず、その本質は別個のオペコードであり、ここでは疑似関数または命令関数と呼びます。 上で述べたように、疑似関数は標準関数と同じように使用され、同じ特性を持つように見えます。ただし、それらが最終的に実行されるとき、それらは zend によって呼び出し用の対応する命令 (オペコード) に反映されるため、その実装は if、for、算術演算などの演算に近くなります。 php の疑似関数 1. isset 2. empty 3. unset 4 .eval 上記の説明から、疑似関数は実行命令に直接変換されるため、通常の関数に比べて関数呼び出しによるオーバーヘッドが 1 つ少なく、パフォーマンスが向上することがわかります。以下のテストで比較してみます。 Array_key_exists と isset は両方とも、配列内にキーが存在するかどうかを判断できます。パフォーマンスを見てみましょう 図からわかるように、array_key_exists と比較すると、isset のパフォーマンスが向上しています。は大幅に高く、基本的には前者の約 4 倍であり、空の関数呼び出しと比較しても約 1 倍のパフォーマンスが高くなります。これは、PHP 関数呼び出しのオーバーヘッドがまだ比較的大きいことも証明しています。
以上がPHPの機能原理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。