Почему требуется один раз так плохо использовать?


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

Почему это так?

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

Author: rink.attendant.6, 2008-10-09

14 answers

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.
 103
Author: Oli, 2015-01-27 15:00:07

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

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

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

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

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

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

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

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

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

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

 145
Author: Edward Z. Yang, 2008-10-12 02:05:57

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

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

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

?>

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

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

Технология Вашей Вселенной подразумевает, что 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

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

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

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

 64
Author: terson, 2015-10-29 19:41:44

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

 20
Author: nickf, 2008-10-09 08:27:36

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

 6
Author: Greg, 2008-10-09 08:05:28

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

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

Подробнее о require_once() в Создайте свою Вселенную.

 5
Author: Annika Backstrom, 2008-10-09 12:35:01

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

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

  • требуется включить путь для того, чтобы использовать пакет (ГРУША). Это затрудняет объединение пакета PEAR в другое приложение с собственным путем include_path, создание одного файла, содержащего необходимые классы, перемещение пакета PEAR в архив phar без значительных изменений исходного кода.
  • когда require_once верхнего уровня смешивается с условным require_once, это может привести к тому, что код не будет доступен для кэширования с помощью кодов операций, таких как APC, который будет поставляться в комплекте с PHP 6.
  • относительный require_once требует, чтобы include_path уже был настроен на правильное значение, что делает невозможным использование пакета без надлежащего include_path
 5
Author: Steve Clay, 2013-05-17 03:22:05

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

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


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

 5
Author: hexalys, 2016-11-14 22:43:28

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

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

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

 2
Author: NeuroXc, 2015-01-27 13:14:09

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

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

 0
Author: Lucas Oman, 2008-10-09 12:45:59

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

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

 0
Author: Dinoboff, 2018-01-08 02:22:22

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

 -2
Author: Ekkmanz, 2008-10-09 08:42:48

Это не имеет никакого отношения к скорости. Речь идет о том, чтобы изящно потерпеть неудачу.

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

 -3
Author: ashchristopher, 2008-10-12 02:11:59

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

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

 -4
Author: Joe Scylla, 2008-10-09 10:18:33