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

143

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

Почему это?

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

Uberfuzzy
источник
9
Этот вопрос сейчас довольно старый, и ответы на него уже не актуальны. Было бы здорово увидеть обновленный набор ответов от участников :)
Purefan

Ответы:

108

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.
Oli
источник
29
Я сомневаюсь, что ваш метод define () работает быстрее, чем встроенная таблица поиска, но я согласен с вашей общей точкой зрения - безусловно, не проблема ?!
Бобби Джек
1
Я вполне уверен, что ты прав, Бобби, но я не защищаю определения через _once. Это просто вариант. Время, которое потребуется для интерпретации кода, может даже сделать его несколько медленнее, но, тем не менее, я не знаю, насколько тщательным является внутренний метод. Это может сделать дополнительную работу, чтобы избежать дубликатов.
Оли
8
Другим недостатком является то, что APC не
кэширует
3
Я только что провел очень простой тест этих двух методов - я сделал 1 000 000 итераций, включая файл, который просто определял константу 'testinclude' в true. В первом тесте я использовал require_once, второй я использовал if (!определен ('testinclude')), и результаты были интересны: Require: 0.81639003753662 Не определено: 0.17906713485718 Определено на 0.63732290267944 микросекунд быстрее.
Трэвис Уэстон
Случай с define будет быстрее, так как вы не выполняете никаких дополнительных проверок, таких как использование realpath. Это не сравнение двух действительно одинаковых вещей.
jgmjgm
150

Эта тема заставляет меня передергиваться, потому что уже было опубликовано «решение», и это, по сути, неправильно. Давайте перечислим:

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

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

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

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

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

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

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

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

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

Эдвард З. Ян
источник
14
Это 4 года и по большей части больше не применяется define(), require_once()и defined()все занимает около 1-2 микросекунд каждый на моей машине.
Дэниэл Бердсли
72
Но это на 2 микросекунды раньше, чем у пользователя появится страница. Более года просмотра страниц, что может сэкономить пользователю целых 3 секунды! Они могли смотреть одну десятую рекламы за это время! Подумайте о пользователе. Не тратьте микросекунды.
Эндрю Энсли
15
Точно так же, как все знают о сарказме, микросекунда составляет 1/1000000 секунды.
Энди Чейз
1
@AndrewEnsley Вы просто ошибаетесь со всем своим сарказмом. Вы не знаете, что PHP также работает на микропроцессорах, 1 микросекунда на вашем ПК - это несколько миллисекунд на микропроцессоре. Теперь, что с 20 включаемыми файлами, большим проектом? Это 20-кратная задержка в несколько миллисекунд, поэтому мы уже достигли точки, которая заметна для человека. Если этот сценарий вызывается часто, это приведет к проблемам с производительностью в системе. Оптимизация не шутка, и весь мир не поворачивается вокруг вашего ПК. Используется десять тысяч процессоров.
Джон
2
@John. Это была шутка, сделанная в хорошем настроении. Злоба не предназначена. Если это стоит того, чтобы оптимизировать ваши включения, продолжайте.
Эндрю Энсли
66

Мне стало любопытно и проверил ссылку Адама Бэкстрома на Tech Your Universe . В этой статье описывается одна из причин, по которой нужно использовать require вместо require_once. Однако их претензии не выдержали моего анализа. Мне было бы интересно узнать, где я мог неправильно проанализировать решение. Я использовал 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 при включении заголовочных файлов. Затем я создал 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, которые использовали файл заголовка, похожий на:

<?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 пользовательских и 72,6 системных запросов; использование require использует в среднем 39,4 пользовательских и 72,0 системных. Таким образом, кажется, что загрузка немного ниже, используя 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

Это контрастирует с требованием:

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

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

Паттерсона
источник
6
И любая «оптимизация», которую вам нужно выполнить 10000 раз, чтобы увидеть столь незначительную разницу, даже не стоит беспокоиться. Используйте профилировщик и узнайте, где в вашем приложении есть настоящие узкие места. Я сомневаюсь, что этот вопрос является узким местом.
DGM
1
Все это означает, что это не имеет значения вообще. Используйте все, что работает лучше для вас логически.
Баттл Буткус
Ват это беспорядки
OverCoder
21

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

nickf
источник
во- первых , chazzuka.com/blog/?p=163 они действительно не «не», но слишком много «дорогих» вещей складываются. и на самом деле, все включенные / обязательные файлы добавляются во внутренний массив (есть функция для его возврата), я думаю, _once придется зацикливать этот массив и делать strcmp, что
суммирует
7

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

Greg
источник
3
но самый первый пример на странице объектов автозагрузки, на которую вы
ссылались,
Я не покупаю это. Есть МНОЖЕЕ ситуаций, в которых ОО не подходит так же хорошо, как другие парадигмы, поэтому вы не должны заставлять его просто получать какие-либо крошечные преимущества, которые могут быть с __autoload ().
Бобби Джек
1
можно подумать, что автозагрузка на самом деле займет больше времени, чем * _once (при условии, что вам требуется только то, что вам нужно).
Nickf
Нет, это не так, по крайней мере, определенно, автозагрузку все равно нужно как-то включать, и это последнее средство для PHP перед ошибкой - поэтому в действительности PHP фактически выполняет потенциально ненужные проверки во всех местах, которые будут применяться для включения / запроса и ПОСЛЕ ЧТО бы это вызвало автозагрузку (если она определена) ... PS: __autoload()не рекомендуется, и в будущем это может быть устаревшим, вы должны использовать spl_autoload_register(...)эти дни ... PS2: не поймите меня неправильно, иногда я использую функцию автозагрузки; )
jave.web
6

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

Люди не должны думать, что 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 класс предназначен для использования всех трех других классов. Справедливо!

Но что теперь, если посетитель просматривает сайт и даже не вошел в систему, а фреймворк загружается: require_once("includes/user.php"); для каждого отдельного запроса.

Он включает 1 + 3 ненужных класса, которые он никогда не будет использовать во время этого конкретного запроса. Вот как раздутые фреймворки в итоге используют 40 МБ на запрос, а не 5 МБ или меньше.


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

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

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

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Функция полезна , когда необходимо, но это не следует рассматривать как монолитное решение использовать везде для загрузки всех классов.

hexalys
источник
5

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

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

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

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

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

Больше на require_once()это в Технологическом вашей вселенной .

Анника Бэкстрем
источник
Спасибо за указатель на статью. require_once () - это хороший ремень безопасности для двойных включений файлов, и мы будем продолжать его использовать, но иметь возможность очистить его - это хорошо.
Энди Лестер
2

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

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

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

NeuroXc
источник
0

Вы проверяете, используя include, альтернативу oli и __autoload (); и проверить это с чем-то вроде APC установленным .

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

Dinoboff
источник
0

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

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

Лукас Оман
источник
-2

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

Ekkmanz
источник
Некоторые ссылки будут в порядке.
Питер Мортенсен
-3

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

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

ashchristopher
источник
1
Не обязательно. На самом деле вы можете подключить обработчик ошибок или обработчик завершения работы, чтобы предоставить пользователю хорошую страницу ошибок (хотя люди редко делают это). Как разработчик, я бы предпочел ошибки сразу.
Эдвард З. Ян
1
Или, в зависимости от обстоятельств, без изящных сбоев - если какой-то жизненно важный файл не требует (), то было бы неплохо просто сдаться и остановиться. Но это требует vs include, в то время как я думаю, что вопрос более сфокусирован на требовании vs require_once.
HoboBen
-4

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

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

Джо Сцилла
источник