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


143

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

Почему это?

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


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.

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


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

7

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


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


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

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


0

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

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


0

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

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


-2

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


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

-3

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

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


1
Не обязательно. На самом деле вы можете подключить обработчик ошибок или обработчик завершения работы, чтобы предоставить пользователю хорошую страницу ошибок (хотя люди редко делают это). Как разработчик, я бы предпочел ошибки сразу.
— Эдвард З. Ян

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

-4

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.