php - require_onceが使いにくいのはなぜですか?


より良いPHPコーディング実践について読んだことは、速度のためにrequire_once使わないということを続けています。

どうしてこれなの?

require_onceと同じことを行うための適切な方法は何ですか? それが問題なら、私はPHP5を使用しています。



Answers



require_onceinclude_once両方とも、システムが既に含まれている/必要とされているもののログを保持することを要求します。 すべての*_once呼び出しは、そのログをチェックすることを意味します。 だから間違いなくそこで行われている余分な仕事があるが、アプリ全体のスピードを損なうほど十分だろうか?

...私は本当にそれを疑う...あなたが本当に古いハードウェア上にいないか、それほど多くをやっていない限り。

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

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

私は個人的に*_onceステートメントに固執しますが、100万パスのベンチマークでは、2つの違いが分かります。

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

require_once 10〜100倍遅くなり、 require_oncehhvm一見遅くなるのがhhvmです。 繰り返しますが、 *_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.



このスレッドは、すでに "解決策が投稿されました"という理由で私を嫌にしています。それは、すべての目的と目的のために間違っています。 列挙しましょう:

  1. 定義はPHPで本当に高価です。 それたり自分でテストたりすることはできますが、PHPでグローバル定数を定義する唯一の効率的な方法は、拡張を介してです。 (クラスの定数は実際にはかなり賢明なパフォーマンスの賢明ですが、これは不都合な点です、2)

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

ですから、しばらくの間、多くの人がその包含にclass_exists()メソッドを使用しました。 私はそれが醜いので好きではありませんが、 require_once()はいくつかの最新バージョンのPHPの前にはかなり非効率的でした。 しかし、これは修正されています。条件付きでコンパイルしなければならない余分なバイトコードと余分なメソッド呼び出しは、内部ハッシュテーブルのチェックをはるかに上回ります。

今、入場料:このことはテストするのが難しいです。実行時間が非常に少ないためです。

インタプリタがヒットするたびに解析モードに戻り、オペコードを生成してから戻る必要があるため、原則としてPHPではコストがかかるので、これを考慮する必要があります。 100以上のインクルードをお持ちの場合、これは間違いなくパフォーマンスに影響を与えます。 require_onceを使用するか使用しないかが重要な問題である理由は、オペコード・キャッシュの寿命を延ばすためです。 これに関する説明はここにありますが、これは次のようなものです。

  • 解析時間中に要求の全ライフサイクルにわたって必要なインクルードファイルが何であるかを正確に知っていれば、最初のものをrequire()すれば、opcodeキャッシュが他のものをすべて処理します。

  • オペコードキャッシュを実行していない場合、あなたは難しい場所にいます。 すべてのインクルードを1つのファイルにインライン展開します(開発中は、プロダクションでのみ行います)。これは確かに時間を解析するのに役立ちますが、苦労します。要求。

  • オートロードは非常に便利ですが、インクルードが完了するたびにオートロードロジックを実行する必要があるため、遅いです。 実際には、1つのリクエストに対して複数の特殊なファイルを自動ロードするだけでは問題は発生しませんが、必要なすべてのファイルを自動ロードするべきではありません。

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




私は不思議に思って、 Tech Your UniverseへのAdam Backstromのリンクをチェックしました。 この資料でrequire_onceの代わりにrequireを使用する必要がある理由の1つについて説明します。 しかし、彼らの主張は私の分析を支持するものではなかった。 私は解決策を誤解している可能性がある場所を見ることに興味があります。 私は比較のためにPHP 5.2.0を使用しました。

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

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

?>

私はこれらをquick bash hackを使って作成しました:

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

こうすることで、require_onceとヘッダファイルをインクルードする際に簡単に入れ替えることができます。 私は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 w/ 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ヘッダーを対照しました:

<?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ユーザーのjiffiesと72.6システムのjiffiesを使用しています。 平均39.4ユーザーのjiffiesと72.0のシステムjiffiesでrequireを使用します。 したがって、require_onceを使用すると負荷がわずかに低下するようです。 しかし、壁時計はわずかに増加します。 10,000件のrequire_onceコールは平均10.15秒で完了し、10,000件のコールは平均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

他のシステムコールはgetcwd()です:

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

これは、hdrXXXファイルで参照される相対パスに決めたために呼び出されます。 これを絶対参照にすると、唯一の違いはコードで追加された時間(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

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

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

長い投稿を申し訳ありません。 私は不思議に思った。




あなたはそれを避けるために言うこれらのコーディング慣行へのリンクを私達に与えることができますか? 限り、私は懸念している、 それは完全な非問題です。 私は自分自身でソースコードを見ていませんが、 includeinclude_once唯一の違いは、 include_onceがそのファイル名を配列に追加し、その都度配列をチェックするということです。 その配列をソートしたままにするのは簡単でしょうから、それを検索するのはO(log n)でなければならず、中規模のアプリケーションでさえ数十含まれています。




より良い方法は、オブジェクト指向のアプローチを使用し、 __autoload()を使用することです。




*_once()関数はすべての親ディレクトリをstatして、あなたがインクルードしているファイルが既にインクルードされているものと同じでないことを確認します。 これは減速の理由の一部です。

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

Tech Your Universeの require_once()詳細




PEAR2 wiki(存在していたとき)は、少なくともライブラリコードのために、 autoloadingのためにrequire / includeディレクティブをすべて破棄しなければならない理由を列挙していました。 これは、 pharのような代替パッケージングモデルが地平線上にあるときに、厳格なディレクトリ構造につなぎます。

更新:wikiのWebアーカイブバージョンが目障りに醜いので、私は以下の最も説得力のある理由をコピーしました:

  • include_pathは、(PEAR)パッケージを使用するために必要です。 これにより、独自のinclude_pathを持つ別のアプリケーションにPEARパッケージをバンドルして、必要なクラスを含む単一のファイルを作成し、広範なソースコードを変更せずにPEARパッケージをpharアーカイブに移動することが困難になります。
  • トップレベルのrequire_onceが条件付きrequire_onceと混在すると、PHP 6にバンドルされるAPCなどのオペコードキャッシュによってキャッシュ不可能なコードが生成される可能性があります。
  • 相対的なrequire_onceは、include_pathがすでに正しい値に設定されていることを必要とするため、適切なinclude_pathなしでパッケージを使用することは不可能です



それは悪い機能を使用していません。 全体的なコードベースで、いつどのように使用するのかを間違って理解しています。 私はちょっとだけ、誤解を招くような文脈を追加します:

人々はrequire_onceが遅い関数であると考えるべきではありません。 あなたはあなたのコードを一方的にまたは別の方法で含める必要があります。 require_once()require()の速度は問題ではありません。 それは、それを盲目的に使用する結果となる可能性のある警告の実行を妨げる性能についてです。 コンテキストを考慮せずに広く使用すると、膨大なメモリの無駄や無駄なコードが発生する可能性があります。

私が本当に悪いと思っていたのは、巨大なモノリシックなフレームワークが、特に複雑なオブジェクト指向の環境で、すべての間違った方法でrequire_once()を使うときです。

多くのライブラリで見られるように、すべてのクラスの先頭にrequire_once()を使用する例を考えてみましょう:

require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
  //user functions
}

したがって、 Userクラスは他の3つのクラスをすべて使用するように設計されています。 けっこうだ! しかし、訪問者がサイトをブラウズしていてログインしていなくても、フレームワークがロードされた場合はどうなりますか? require_once("includes/user.php"); 1回のリクエストごとに

それは、その特定の要求の間に使用されない1 + 3の不要なクラスを含みます。 これは、肥大化したフレームワークが、5MB以下ではなく、要求ごとに40MBを使用することになります。

それが誤用される可能性がある他の方法は、クラスが他の多くのものによって再利用されるときです! helper関数を使用する約50のクラスがあるとします。 helpersがロードされているときにhelpersを利用できるようにするには、次のようにします。

require_once("includes/helpers.php");
class MyClass{
  //Helper::functions();//etc..
}

ここには何も問題はありません。 しかし、1ページのリクエストに15の類似クラスが含まれているとします。 あなたはrequire_once 15回実行しています。

require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");

require_once()を使用すると、不要な行を解析する必要がなく、技術的に14回その関数を実行する際のパフォーマンスに影響を与えます。 同じような問題を抱えている他のクラス10人ほどで、それほど無意味な繰り返しコードの100行以上を占める可能性があります。

それで、おそらくrequire("includes/helpers.php");を使う価値がrequire("includes/helpers.php"); あなたのアプリやフレームワークのブートストラップで、代わりに。 しかし、すべてが相対的であるためhelpersクラスの重みと使用頻度がrequire_once() 15〜100行を節約する価値があるどうかによって異なります。 しかし、任意のリクエストでhelpersファイルを使用しない確率がnoneの場合、 requireは必ずメインクラスにrequire必要があります。 各クラスにrequire_onceを別々に持つことは、リソースの無駄になります。

require_once関数は必要なときに便利ですが、すべてのクラスをロードするためにあらゆる場所で使用するモノリシックなソリューションとはみなされません。




require_onceinclude_once requireincludeよりも遅い場合でも(ここでinclude代替案が存在する可能性があります)、ここではマイクロ最適化の最小レベルについて説明します。 あなたの時間は、 require_onceようなことを気にするよりも、書きづらいループやデータベースのクエリを最適化するのにずっと効果的です。

さて、 require_onceはあなたのインクルードをきれいに整理しておくことに注意を払う必要はないので、 require_onceはコーディングの慣習が貧弱だと言う議論をすることができますが、それは関数そのものとは無関係で、特にスピードではありません。

明らかに、コードのクリーンさとメンテナンスの容易さのためにオートローディングが優れていますが、これはスピードとは関係がないことを明確にしたいと思います。




あなたは、include、oliの代替と__autoload()を使ってテストします。 APCがインストールされた状態でテストしてください。 私は物事をスピードアップする定数を使用して疑う。




はい、それは普通のol 'require()よりも若干高価です。 私は、あなたがコードをいくつかのサイクルを節約するので、* _once()関数を使わないでください。

しかし、_once()関数を使用することはあなたのアプリを終了させません。 基本的には、 あなたのインクルードを整理する必要がないという口実として使ってはいけません 。 場合によっては、それを使用することはやむを得ないことであり、大きな問題ではありません。




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




それはスピードとは関係ありません。 それは優雅に失敗することです。

require_once()が失敗すると、スクリプトが実行されます。 それ以外は処理されません。 include_once()を使用すると、スクリプトの残りの部分がレンダリングを続けようとするので、スクリプトで失敗したことにユーザーが夢中になることはありません。




私の個人的な意見はrequire_once(またはinclude_once)の使用法は悪い習慣です。あなたがすでにそのファイルをインクルードしている場合はrequire_onceがチェックし、重複したファイルのエラーは抑制します(関数/クラス/などの重複宣言のような) 。

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