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

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

Почему это?

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

Ответ 1

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

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

Если вы делаете тысячи *_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.

Ответ 2

Эта нить заставляет меня съеживаться, потому что уже было "решение опубликовано", и это, во всех смыслах и целях, неверно. Позвольте перечислить:

  • Определяет действительно дорогостоящий PHP. Вы можете просмотреть его или протестировать его самостоятельно, но единственным эффективным способом определения глобальной константы в PHP является расширение. (Константы класса на самом деле довольно приличная производительность, но это спорная точка из-за 2)

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

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

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

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

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

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

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

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

Ответ 3

Мне стало любопытно и проверил ссылку Адама Backstrom на Tech Your Universe. В этой статье описывается одна из причин, требующих использования вместо require_once. Однако их претензии не соответствовали моему анализу. Мне было бы интересно увидеть, где я, возможно, неправильно разобрал решение. Я использовал php 5.2.0 для сравнения.

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

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

?>

Я создал их с помощью быстрого bash hack:

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

Таким образом, я могу легко переключаться между использованием require_once и требовать при включении файлов заголовков. Затем я создал 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 с заголовками запросов, в которых использовался заголовочный файл, похожий на:

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

?>

Я не нашел большой разницы при выполнении этого с require vs. require_once. На самом деле мои первоначальные тесты, по-видимому, подразумевали, что require_once был немного быстрее, но я не уверен в этом. Я повторил эксперимент с 10000 входными файлами. Здесь я видел постоянную разницу. Я провел тест несколько раз, результаты близки, но использование require_once использует в среднем 30.8 пользовательских jiffies и 72.6 системных jiffies; использование требует использования в среднем 39,4 пользовательских jiffies и 72.0 системных jiffies. Поэтому оказывается, что нагрузка немного ниже, используя require_once. Однако настенные часы немного увеличены. В 10 000 вызовов require_once используется в среднем 10,15 секунды, а в среднем 10000 вызовов - в среднем 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 для всех прогонов:

[[email protected] phpperf]$ wc -l strace_1000r.out strace_1000ro.out 
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

Эффективно существует около двух системных вызовов в файле заголовка при использовании require_once. Одно из отличий заключается в том, что require_once имеет дополнительный вызов функции time():

[[email protected] phpperf]$ grep -c time strace_1000r.out strace_1000ro.out 
strace_1000r.out:20009
strace_1000ro.out:30008

Другим системным вызовом является getcwd():

[[email protected] phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out 
strace_1000r.out:5
strace_1000ro.out:10004

Это вызвано, потому что я решил относительный путь, указанный в файлах hdrXXX. Если я сделаю это абсолютной ссылкой, то единственным отличием является дополнительный вызов времени (NULL), сделанный в коде:

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Обновление. Поскольку веб-архивная версия вики очень уродливая, я скопировал наиболее веские причины ниже:

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

Ответ 8

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

Люди не должны думать, что require_once - медленная функция. Вы должны включить свой код так или иначе. require_once() vs. 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 предназначен для использования всех трех других классов. Справедливо! Но теперь, если посетитель просматривает сайт и даже не входит в систему, а среда загружает: 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 для любого заданного запроса - нет, тогда require определенно будет в вашем основном классе. Наличие require_once в каждом классе по отдельности становится пустой тратой ресурсов.


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

Ответ 9

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

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

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

Ответ 10

Вы тестируете, используя include, oli alternative и __autoload(); и протестируйте его с помощью что-то вроде APC. Я сомневаюсь, что использование постоянной скорости ускорит процесс.

Ответ 11

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

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

Ответ 12

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

Ответ 13

Это не имеет никакого отношения к скорости. Это о неудачной изящности.

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

Ответ 14

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

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