145

私がより良いPHPコーディング慣行について読んだことはすべてrequire_once、速度のために使用しないでくださいと言い続けています。

どうしてこれなの?

と同じことをするための適切な/より良い方法は何require_onceですか?重要な場合は、PHP5を使用しています。

4

14 に答える 14

148

すでに「解決策が投稿されている」ので、このスレッドは私をうんざりさせます、そしてそれはすべての意図と目的のために、間違っています。列挙してみましょう:

  1. PHPでは定義は非常に高価です。自分で調べたりテストしたりできますが、PHPでグローバル定数を定義する唯一の効率的な方法は拡張機能を使用することです。(クラス定数は実際にはかなり適切なパフォーマンスですが、2のため、これは重要なポイントです)

  2. 適切に使用しているrequire_once()場合、つまりクラスを含める場合は、定義すら必要ありません。かどうかを確認してくださいclass_exists('Classname')require_once()含めるファイルにコードが含まれている場合、つまり手続き型で使用している場合、必要な理由はまったくありません。ファイルを含めるたびに、サブルーチン呼び出しを行っていると推定されます。

そのため、しばらくの間、多くの人がこのclass_exists()方法をインクルージョンに使用していました。それは醜いので私はそれが好きではありませんが、彼らには次のような正当な理由がありました:require_once()PHPの最近のバージョンのいくつかの前はかなり非効率的でした。しかし、これは修正されており、条件付きでコンパイルする必要のある余分なバイトコードと、余分なメソッド呼び出しが、内部ハッシュテーブルチェックをはるかに上回っていることは私の主張です。

さて、認めてください。実行時間のほとんどを占めていないため、このようなものをテストするのは困難です。

考えるべき質問は次のとおりです。インクルードは、原則としてPHPで高価です。これは、インタープリターが1つに到達するたびに、解析モードに切り替えてオペコードを生成してから、元に戻す必要があるためです。100以上のインクルードがある場合、これは間違いなくパフォーマンスに影響します。require_onceを使用するかどうかが非常に重要な質問である理由は、オペコードキャッシュの使用が困難になるためです。これについての説明はここにありますが、これは要約すると次のようになります。

  • 解析時に、リクエストの全期間に必要なインクルードファイルが正確にわかっている場合は、require()最初のファイルとオペコードキャッシュが他のすべてを処理します。

  • オペコードキャッシュを実行していない場合は、困難な状況にあります。すべてのインクルードを1つのファイルにインライン化する(開発中はこれを行わないでください。本番環境でのみ行う)ことは確かに時間を解析するのに役立ちますが、それを行うのは面倒です。また、インクルードする内容を正確に知る必要があります。リクエスト。

  • 自動ロードは非常に便利ですが、インクルードが実行されるたびに自動ロードロジックを実行する必要があるため、低速です。実際には、1つのリクエストに対して複数の特殊なファイルを自動ロードしてもそれほど問題は発生しないことがわかりましたが、必要なすべてのファイルを自動ロードするべきではありません。

  • おそらく10個のインクルードがある場合(これはエンベロープ計算の非常に裏側です)、この手コキはすべて価値がありません。データベースクエリなどを最適化するだけです。

于 2008-10-12T02:05:57.840 に答える
115

require_onceinclude_onceどちらも、システムがすでに含まれている/必要なもののログを保持する必要があります。すべての*_once呼び出しは、そのログを確認することを意味します。余分な作業が行われていることは間違いありませんが、アプリ全体の速度を損なうのに十分ですか?

... 私はそれを本当に疑っています... あなたが本当に古いハードウェアを使っているか、それをたくさんやっているのでない限り。

何千ものを実行している場合*_onceは、より軽い方法で自分で作業を行うことができます。単純なアプリの場合、一度だけ含めたことを確認するだけ十分ですが、それでも再定義エラーが発生する場合は、次のようにすることができます。

if (!defined('MyIncludeName')) {
    require('MyIncludeName');
    define('MyIncludeName', 1);
}

私は個人的に*_onceステートメントに固執しますが、ばかげたミリオンパスのベンチマークでは、2 つの違いを見ることができます。

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

で 10 ~ 100 倍遅くなり、で一見遅くrequire_onceなったのは興味深いことです。繰り返しますが、これは何千回も実行している場合にのみコードに関係します。require_oncehhvm*_once


<?php // test.php

$LIMIT = 1000000;

$start = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    if (!defined('include.php')) {
        require('include.php');
        define('include.php', 1);
    }

$mid = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    require_once('include.php');

$end = microtime(true);

printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);

<?php // include.php

// do nothing.
于 2008-10-09T08:20:31.480 に答える
68

私は興味を持って、Adam Backstrom のTech Your Universeへのリンクを調べました。この記事では、require_once の代わりに require を使用する必要がある理由の 1 つについて説明します。しかし、彼らの主張は私の分析に耐えられませんでした。ソリューションを誤って分析した可能性がある場所を確認したいと思います。比較には PHP 5.2.0 を使用しました。

まず、require_once を使用して別のヘッダー ファイルをインクルードする 100 個のヘッダー ファイルを作成しました。これらの各ファイルは次のようになります。

<?php
    // /home/fbarnes/phpperf/hdr0.php
    require_once "../phpperf/common_hdr.php";

?>

簡単な Bash ハックを使用してこれらを作成しました。

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
    echo "<?php
    // $i" > $i
    cat helper.php >> $i;
done

このようにして、ヘッダー ファイルをインクルードするときに、require_once と require の使用を簡単に切り替えることができました。次に、100 個のファイルをロードする app.php を作成しました。これは次のように見えました:

<?php
    // Load all of the php hdrs that were created previously
    for($i=0; $i < 100; $i++)
    {
        require_once "/home/fbarnes/phpperf/hdr$i.php";
    }

    // Read the /proc file system to get some simple stats
    $pid = getmypid();
    $fp = fopen("/proc/$pid/stat", "r");
    $line = fread($fp, 2048);
    $array = split(" ", $line);

    // Write out the statistics; on RedHat 4.5 with kernel 2.6.9
    // 14 is user jiffies; 15 is system jiffies
    $cntr = 0;
    foreach($array as $elem)
    {
        $cntr++;
        echo "stat[$cntr]: $elem\n";
    }
    fclose($fp);
?>

次のようなヘッダー ファイルを使用する require_once ヘッダーと、require_once ヘッダーを比較しました。

<?php
    // /home/fbarnes/phpperf/h/hdr0.php
    if(!defined('CommonHdr'))
    {
        require "../phpperf/common_hdr.php";
        define('CommonHdr', 1);
    }
?>

これをrequireとrequire_onceで実行しても、大きな違いは見つかりませんでした。実際、私の最初のテストでは、require_once の方がわずかに高速であることが示されていましたが、必ずしもそうではありません。10000 個の入力ファイルで実験を繰り返しました。ここでは、一貫した違いが見られました。テストを複数回実行しました。結果は近いですが、require_once を使用すると、平均で 30.8 ユーザー jiffy と 72.6 システム jiffy が使用されます。require を使用すると、平均で 39.4 ユーザー jiffy と 72.0 system jiffy が使用されます。そのため、require_once を使用した方が若干負荷が低いように見えます。ただし、壁時計の時間はわずかに増加します。10,000 回の require_once 呼び出しは平均で 10.15 秒を使用して完了し、10,000 回の require 呼び出しは平均で 9.84 秒を使用します。

次のステップは、これらの違いを調べることです。straceを使用して、行われているシステム コールを分析しました。

require_once からファイルを開く前に、次のシステム コールが行われます。

time(NULL)                              = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL)                              = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

これは、require とは対照的です。

time(NULL)                              = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL)                              = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Tech Your Universe は、require_once がより多くの lstat64 呼び出しを行う必要があることを意味します。ただし、どちらも同じ数の lstat64 呼び出しを行います。おそらく違いは、上記のコードを最適化するために APC を実行していないことです。ただし、次に、実行全体の strace の出力を比較しました。

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

実際には、require_once を使用すると、ヘッダー ファイルごとに約 2 つのシステム コールが追加されます。1 つの違いは、require_once には time() 関数への追加の呼び出しがあることです。

[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008

もう 1 つのシステム コールは getcwd() です。

[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004

これは、hdrXXX ファイルで参照される相対パスに決めたために呼び出されます。これを絶対参照にすると、唯一の違いは、コードで行われる追加の time(NULL) 呼び出しです。

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
  190705 strace_1000r.out
  200705 strace_1000ro.out
  391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008

これは、相対パスではなく絶対パスを使用することで、システム コールの数を減らすことができることを意味しているようです。それ以外の唯一の違いは、コードを計測して高速なものを比較するために使用されるように見える time(NULL) 呼び出しです。

もう 1 つの注意点として、APC 最適化パッケージには「apc.include_once_override」というオプションがあり、require_once および include_once 呼び出しによって行われるシステム呼び出しの数を減らすことができると主張しています ( PHP のドキュメントを参照してください)。

于 2008-10-12T01:43:19.477 に答える
21

それを避けるように言っているこれらのコーディング慣行へのリンクを教えてください。私に関する限り、それはまったく問題ではありません。私は自分でソースコードを見たことはありませんが、 と の唯一の違いは、includeそのファイル名を配列に追加し、配列を毎回チェックすることだと思います。その配列をソートしたままにしておくのは簡単なので、検索は O(log n) である必要があり、中規模のアプリケーションでさえ数十のインクルードしかありません。include_onceinclude_once

于 2008-10-09T08:27:36.257 に答える
7

物事を行うためのより良い方法は、オブジェクト指向のアプローチを使用し、__ autoload()を使用することです。

于 2008-10-09T08:05:28.483 に答える
5

PEAR2 wiki (存在していたとき)は、少なくともライブラリ コードについては、オートロードを優先してすべての require/include ディレクティブを放棄する正当な理由をリストしていました。これらは、 pharのような代替パッケージ モデルが間近に迫っているときに、厳格なディレクトリ構造に縛られます。

更新: Wiki の Web アーカイブ バージョンは目を奪われるほど醜いため、最も説得力のある理由を以下にコピーしました。

  • (PEAR) パッケージを使用するには、include_path が必要です。これにより、独自の include_path を使用して別のアプリケーション内に PEAR パッケージをバンドルしたり、必要なクラスを含む単一のファイルを作成したり、ソース コードを大幅に変更せずに PEAR パッケージを phar アーカイブに移動したりすることが困難になります。
  • トップレベルの require_once が条件付きの require_once と混在している場合、PHP 6 にバンドルされる予定の APC などのオペコード キャッシュではキャッシュできないコードになる可能性があります。
  • 相対 require_once では、include_path が既に正しい値に設定されている必要があるため、適切な include_path なしでパッケージを使用することはできません。
于 2008-10-11T04:55:35.750 に答える
5

*_once()関数は、すべての親ディレクトリを統計して、含めるファイルが既に含まれているファイルと同じではないことを確認します。それが減速の理由の一部です。

ベンチマークにはSiegeなどのツールを使用することをお勧めします。提案されたすべての方法論を試して、応答時間を比較できます。

詳細require_once()Tech Your Universeにあります。

于 2008-10-09T12:35:01.640 に答える
0

インクルード、oli の代替、および __autoload(); を使用してテストします。APCのようなものをインストールしてテストします。

定数を使用すると速度が上がるとは思えません。

于 2008-10-09T10:04:49.353 に答える
0

はい、普通の require() よりも少し高価です。インクルードが重複しないようにコードを整理しておくことができる場合は、 *_once() 関数を使用しないでください。サイクルを節約できるためです。

しかし、_once() 関数を使用しても、アプリケーションが強制終了されることはありません。基本的に、includes を整理する必要がないという言い訳として使用しないでください。場合によっては、それを使用することは依然として避けられず、大したことではありません。

于 2008-10-09T12:45:59.853 に答える
-3

それは速度とは何の関係もありません。それは優雅に失敗することについてです。

require_once()が失敗した場合、スクリプトは完了です。他には何も処理されません。include_once()を使用すると、スクリプトの残りの部分がレンダリングを続行しようとするため、ユーザーは、スクリプトで失敗したものに対して賢明ではない可能性があります。

于 2008-10-12T02:11:59.637 に答える
-3

PEAR のドキュメントでは、require、require_once、include、および include_once の推奨事項があると思います。私はそのガイドラインに従っています。あなたのアプリケーションはより明確になります。

于 2008-10-09T08:42:48.957 に答える
-4

私の個人的な意見では、require_once (または include_once) の使用は悪い習慣です。なぜなら、require_once は、そのファイルが既にインクルードされているかどうかをチェックし、二重にインクルードされたファイルのエラーを抑制して、致命的なエラー (関数/クラス/などの重複宣言など) を引き起こすからです。 .

ファイルを含める必要があるかどうかを知っておく必要があります。

于 2008-10-09T10:18:33.270 に答える