web-dev-qa-db-ja.com

require_onceの使用がなぜそんなに悪いのですか?

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

どうしてこれなの?

require_onceと同じことを行うための適切/より良い方法は何ですか?問題があれば、PHP5を使用しています。

140
Uberfuzzy

_require_once_と_include_once_の両方では、システムがすでに含まれている/必要なもののログを保持する必要があります。すべての_*_once_呼び出しは、そのログをチェックすることを意味します。そこで、間違いなくsomeが行われていますが、アプリ全体の速度を損なうのに十分ですか?

...本当に疑います... 本当に古いハードウェアを使用している場合、またはlotを実行している場合を除きます。

are数千の_*_once_を実行している場合、自分でより軽い方法で作業を行うことができます。シンプルなアプリの場合は、一度だけインクルードしたことを確認してくださいshouldで十分ですが、再定義エラーが引き続き発生する場合は、次のようにすることができます。

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

私は個人的に_*_once_ステートメントに固執しますが、愚かな百万パスのベンチマークでは、2つの違いを見ることができます:

_                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678
_

_require_once_で10〜100倍遅くなり、hhvmで_require_once_が一見遅くなっているように見えます。繰り返しますが、これは_*_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.
_
104
Oli

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

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

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

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

さて、入場料:実行時間のほとんどを占めないので、このようなものはテストするのが難しいです。

ここで考えなければならない質問があります。インクルードは、一般的なルールとして、インタープリターがヒットするたびに解析モードに切り替え、オペコードを生成してからジャンプするため、PHPでは高価です。 100以上のインクルードがある場合、これは間違いなくパフォーマンスに影響します。 require_onceを使用するか使用しないかが重要な問題である理由は、オペコードキャッシュの寿命が短くなるためです。 これの説明 はここにありますが、これは最終的に次のようになります:

  • 解析中に、リクエストの全期間に必要なインクルードファイルを正確に把握している場合、require()冒頭にあるものとopcodeキャッシュが他のすべてを処理します。

  • オペコードキャッシュを実行していない場合は、難しい場所にいます。すべてのインクルードを1つのファイルにインライン化すること(開発中に実稼働のみでこれを行わないでください)は確かに時間の解析に役立ちますが、それは実行するのが面倒であり、また、要求。

  • 自動ロードは非常に便利ですが、インクルードが完了するたびに自動ロードロジックを実行する必要があるため、時間がかかります。実際には、1つの要求に対していくつかの特殊なファイルを自動ロードすることで問題が大きくなることはありませんが、必要なすべてのファイルを自動ロードすることはできません。

  • 10個のインクルードがある場合(これはエンベロープ計算のveryです)、このすべての無駄は価値がありません:データベースクエリなどを最適化するだけです。

147
Edward Z. Yang

私は興味を持ち、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 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を使用します。 requireを使用すると、平均39.4ユーザーjiffiesおよび72.0システムjiffiesが使用されます。したがって、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

他のシステムコールは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 docs を参照)。

長い投稿でごめんなさい。私は興味を持ちました。

64
terson

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

20
nickf

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

6
Greg

autoload ingを支持するrequire/includeディレクティブを放棄するallの正当な理由をリストするために使用されたPEAR2 wiki(存在する場合) 、少なくともライブラリコードの場合。 phar のような代替パッケージングモデルが水平線上にある場合、これらは固定ディレクトリ構造に拘束されます。

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

  • (PEAR)パッケージを使用するには、include_pathが必要です。これにより、PEARパッケージを独自のinclude_pathを使用して別のアプリケーションにバンドルし、必要なクラスを含む単一ファイルを作成し、PEARパッケージを大規模なソースコードの変更を行わないpharアーカイブ。
  • 最上位のrequire_onceが条件付きrequire_onceと混在している場合、これはPHP 6にバンドルされるAPCなどのオペコードキャッシュによってキャッシュできないコードになります。
  • 相対require_onceでは、適切なinclude_pathなしでパッケージを使用できないようにするために、include_pathがすでに正しい値に設定されている必要があります
5
Steve Clay

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

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

Tech Your Universerequire_once()の詳細。

5

悪い機能を使用していません。全体的なコードベースで、それをいつどのように使用するかについての誤った理解です。誤解されている可能性のある概念にもう少しコンテキストを追加します。

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 + 3 不要クラスが含まれ、その特定のリクエスト中に使用されることはありません。これは、肥大化したフレームワークが5MB以下ではなく、リクエストごとに40MBを使用する方法です。


それが悪用される可能性がある他の方法は、クラスが他の多くの人によって再利用されるときです! helper関数を使用する約50のクラスがあるとします。 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行以上を占める可能性があります。

そのため、代わりにアプリまたはフレームワークのbootstrap=でrequire("includes/helpers.php");を使用する価値があります。しかし、すべてが相対的であるため、すべてに依存helpersクラスの重みと使用頻度がrequire_once()の15〜100行を節約する価値がある場合。ただし、特定のリクエストでhelpersファイルを使用しない確率なしの場合、requireは必ずメインクラスに配置する必要があり、各クラスに_require_once_を含めると、リソースが無駄になります。


_require_once_関数は必要なときに役立ちますが、すべてのクラスをロードするためにどこでも使用するモノリシックなソリューションと見なされるべきではありません。

5
hexalys

たとえ require_onceおよびinclude_oncearerequireおよびinclude(または他に存在する可能性のあるもの)よりも遅いため、ここでは最小レベルのマイクロ最適化について説明しています。あなたの時間は、require_once

これで、require_onceは、インクルードをクリーンで整理された状態に保つことに注意を払う必要がないため、貧弱なコーディング慣行を可能にしますが、それは関数itselfとは関係がなく、特にその速度とは関係ありません。

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

2
NeuroXc

はい、それは普通のol 'require()よりわずかに高価です。インクルードを重複させないようにコードを整理しておくことができれば、サイクルを節約できるので、* _ once()関数を使用しないでください。

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

0
Lucas Oman

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

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

0
Dinoboff