Почему require раз так плохо использовать?

все, что я читал о лучших практиках кодирования PHP, продолжает говорить, что не используйте require_once из-за скорости.

почему это?

Что такое правильный/лучший способ сделать то же самое как require_once? Если это имеет значение, я использую PHP5.

14 ответов


require_once и include_once оба требуют, чтобы система вела журнал того, что уже включено/требуется. Каждый *_once вызов означает проверку этого журнала. Так что определенно некоторые дополнительная работа выполняется там, но достаточно, чтобы нанести ущерб скорости всего приложения?

... Очень сомневаюсь... Нет, если вы не на действительно старое оборудование или делать это много.

если вы are делаешь тысячи *_once, вы могли бы сделать работу самостоятельно в более легкой форме. Для простых приложений, просто убедитесь, что вы включили его только один раз должны достаточно, но если вы все еще получаете переопределить ошибки, вы можете что-то вроде этого:

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

Я лично буду придерживаться *_once заявления, но на глупом миллионном тесте вы можете увидеть разницу между ними:

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

10-100× медленнее с require_once и любопытно, что require_once казалось бы, медленнее в 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. Ты можешь!--13-->посмотреть или проверьте его самостоятельно,но единственный эффективный способ определения глобальной константы в PHP - это расширение. (Константы класса на самом деле довольно приличная производительность, но это спорный вопрос, потому что 2)

  2. если вы используете require_once() надлежащим образом, то есть, для включения классов, вам даже не нужно определять, просто проверить, если class_exists('Classname'). Если файл, который вы включаете, содержит код, то есть вы используете его процедурным способом, нет абсолютно никаких причин, что require_once() должны быть необходимым для вас; каждый раз, когда вы включаете файл, вы предполагаете, что вызов подпрограммы.

так что некоторое время многие люди использовали class_exists() способ их включения. Мне это не нравится, потому что это уродливо, но у них были веские причины: require_once() был довольно неэффективным до некоторых из более поздних версий PHP. Но это было исправлено, и я утверждаю, что дополнительный байт-код, который вам придется скомпилировать для условного, и дополнительный вызов метода, намного перевешивают любую внутреннюю проверку хэш-таблицы.

теперь, признание: этот материал трудно проверить, потому что на него приходится так мало исполнения время.

вот вопрос, о котором вы должны думать: включает, как правило, дорого в PHP, потому что каждый раз, когда интерпретатор попадает в один, он должен переключиться обратно в режим синтаксического анализа, генерировать опкоды, а затем прыгать назад. Если у вас есть 100+ включает, это, безусловно, оказывает влияние на производительность. Причина, по которой использование или не использование require_once является таким важным вопросом, заключается в том, что это затрудняет жизнь кэшей опкодов. Ан объяснение это можно найти здесь, но то, что это сводится к тому, что:

  • если во время разбора вы точно знаете, какие файлы вам понадобятся в течение всего срока действия запроса,require() те в самом начале и кэш опкодов будет обрабатывать все остальное за вас.

  • если вы не используете кэш кода операции, вы находитесь в трудном месте. Вставка всех ваших включений в один файл (не делайте этого во время разработки, только в production), безусловно, может помочь проанализировать время, но это больно делать, а также вам нужно точно знать, что вы будете включать во время запроса.

  • автозапуск очень удобен, но медленный, по той причине, что логика автозапуска должна запускаться каждый раз, когда выполняется включение. На практике я обнаружил, что автоматическая загрузка нескольких специализированных файлов для одного запроса не вызывает слишком много проблем, но вы не должны загружать все файлы, которые вы будете необходимость.

  • если у вас есть, возможно, 10 включает (это очень задняя часть расчета конверта), все это дрочить не стоит: просто оптимизируйте свои запросы к базе данных или что-то еще.


мне стало любопытно, и я проверил ссылку Адама Бэкстрома на Технология Вашей Вселенной. В этой статье описывается одна из причин, которые требуют должны использоваться вместо require_once. Однако их утверждения не соответствовали моему анализу. Я был бы заинтересован в том, где я, возможно, misanalysed решение. Для сравнения я использовал php 5.2.0.

я начал с создания 100 заголовочных файлов, которые использовали require_once для включения другого файла заголовка. Каждый из этих файлов выглядело что-то вроде:

<?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 при включении файлов заголовков. Затем я создал приложение.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_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

это контрастирует с требуется:

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 ваша вселенная подразумевает, что 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 существует еще примерно два системных вызова для каждого файла заголовка. Одно отличие в том, что 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

это, кажется, подразумевает, что вы можете уменьшить количество системных вызовов, используя абсолютные пути а не относительные пути. Единственное отличие от этого-вызовы time(NULL), которые, по-видимому, используются для инструментирования кода для сравнения того, что быстрее.

еще одно замечание заключается в том, что пакет оптимизации APC имеет опцию "apc.include_once_override", который утверждает, что он уменьшает количество системных вызовов, выполняемых вызовами require_once и include_once (см. PHP docs).

извините за длинный пост. Мне стало любопытно.


можете ли вы дать нам какие-либо ссылки на эти методы кодирования, которые говорят, чтобы избежать этого? Что касается меня, это не проблема. Я сам не смотрел исходный код, но я бы предположил, что единственная разница между include и include_once это include_once добавляет имя файла в массив и проверяет массив каждый раз. Было бы легко сохранить этот массив отсортированным, поэтому поиск по нему должен быть O (log n), и даже у среднего большого приложения будет только пара из десятка включает.


лучший способ сделать что-то-использовать объектно-ориентированный подход и использовать _ _ autoload ().


на *_once() функции stat каждый родительский каталог, чтобы убедиться, что файл, который вы включаете, не совпадает с тем, который уже был включен. Это одна из причин спада.

Я рекомендую использовать такой инструмент, как осада для сравнения. Вы можете попробовать все предложенные методологии и сравнить время отклика.

подробнее о require_once() at Технология Вашей Вселенной.


Вики PEAR2 (когда она существовала) используется для списка веские причины для отказа все директивы require / include в пользу автозапускаing, по крайней мере, для кода библиотеки. Они привязывают вас к жестким структурам каталогов, когда альтернативные модели упаковки, такие как phar на горизонте.

Update: поскольку веб-архивная версия wiki является уродливой, я скопировал самые веские причины ниже:

  • include_path требуется для использования пакета (PEAR). Это затрудняет связывание пакета PEAR в другом приложении с его собственный include_path, чтобы создать один файл, содержащий необходимые классы, чтобы переместить пакет PEAR в архив phar без обширного исходного кода модификация.
  • когда верхний уровень require_once смешивается с условным require_once, это может привести к коду, который не может быть кэширован опкодом, таким как APC, который будет быть в комплекте с PHP 6.
  • relative require_once требует, чтобы include_path уже был настроен на правильное значение, что делает невозможным использование пакета без собственно в include_path

это не использование функции, которая плоха. Это неправильное понимание того, как и когда его использовать, в общей базе кода. Я просто добавлю немного больше контекста к этому, возможно, неправильно понятому понятию:

люди не должны думать, что require_once является медленной функцией. Вы должны так или иначе включить свой код. require_once() и 's не вопрос. Речь идет о производительности, препятствующей предостережениям, которые могут привести к ее слепому использованию. Если использовано широко без контекста, это может привести к огромным потерям памяти или нерациональный код.

то, что я видел, это действительно плохо, когда огромные монолитные рамки используют 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 лишних классы, которые он никогда не будет использовать во время этого конкретного запроса. Вот как раздутые фреймворки в конечном итоге используют 40 Мб на запрос, а не 5 Мб или меньше.


другие способы его использования-это когда класс повторно используется многими другими! Скажем, у вас около 50 классов, которые используют helper функции. На всякий случай helpers доступны для тех классов, когда они загружены, вы получаете:

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

здесь нет ничего плохого как такового. Однако, если один запрос страницы включает 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"); вместо этого при загрузке вашего приложения или фреймворка. Но поскольку все относительно,все зависит от того если вес против частоты использования helpers класс стоит сохранить 15-100 строк require_once(). Но если вероятность не использовать helpers файл по любому заданному запросу равен none, тогда require определенно должно быть в вашем вместо этого основной класс. Имея require_once в каждом классе отдельно становится пустой тратой ресурсов.


на require_once функция полезна при необходимости, но ее не следует рассматривать как монолитное решение для использования везде для загрузки всех классов.


даже если require_once и include_once are меньше чем require и include (или любые альтернативы могут существовать), мы говорим о наименьшем уровне микро-оптимизации здесь. Ваше время намного лучше потратить на оптимизацию этого плохо написанного цикла или запроса базы данных, чем беспокоиться о чем-то вроде require_once.

теперь можно привести аргумент, говоря, что require_once позволяет для плохих практик кодирования, потому что вам не нужно обращать внимание на сохранение ваших включает чистый и организованный, но это не имеет ничего общего с функцией и особенно не его скорость.

очевидно, что автозапуск лучше ради чистоты кода и простоты обслуживания, но я хочу дать понять, что это не имеет ничего общего с скорость.


Да, это немного дороже, чем обычный ol' require(). Я думаю, дело в том, что если вы можете сохранить свой код достаточно организованным, чтобы не удвоить includes, не используйте функции *_once (), так как это сэкономит вам несколько циклов.

но использование функций _once () не убьет ваше приложение. В принципе, просто не используйте его в качестве предлога, чтобы не организовывать свои включает. В некоторых случаях его использование по-прежнему неизбежно, и это не имеет большого значения.


вы тестируете, используя include, альтернативу oli и _ _ autoload (); и тестируете его с помощью что-то вроде APC установлен.

Я сомневаюсь, что использование константы ускорит процесс.


Я думаю, что в документации PEAR есть рекомендация для require, require_once, include и include_once. Я следую этому руководству. Ваше заявление будет более ясным.


Это не имеет ничего общего со скоростью. Это по поводу не корректно.

Если require_once() не удается, ваш скрипт выполнен. Ничего не обрабатывается. Если вы используете include_once (), остальная часть вашего скрипта попытается продолжить визуализацию, поэтому ваши пользователи потенциально не будут ничего знать о том, что не удалось в вашем скрипте.


мое личное мнение заключается в том, что использование require_once (или include_once) является плохой практикой, потому что require_once проверяет вас, если вы уже включили этот файл, и подавляет ошибки двойных включенных файлов, приводящие к фатальным ошибкам (например, дублирование объявления функций/классов/и т. д.).

вы должны знать, если вам нужно включить файл.