Все, что я читал о лучших практиках кодирования PHP, говорит, что не используйте require_once
из-за скорости.
Почему это?
Как правильно / лучше сделать то же самое, что и require_once
? Если это имеет значение, я использую PHP 5.
Все, что я читал о лучших практиках кодирования PHP, говорит, что не используйте require_once
из-за скорости.
Почему это?
Как правильно / лучше сделать то же самое, что и require_once
? Если это имеет значение, я использую PHP 5.
Ответы:
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.
Эта тема заставляет меня передергиваться, потому что уже было опубликовано «решение», и это, по сути, неправильно. Давайте перечислим:
Определения действительно дорогие в PHP. Вы можете посмотреть его или протестировать самостоятельно, но единственный эффективный способ определения глобальной константы в PHP - через расширение. (Константы класса на самом деле довольно приличны с точки зрения производительности, но это спорный вопрос из-за 2)
Если вы используете require_once()
правильно, то есть для включения классов, вам даже не нужно определение; просто проверьте, если class_exists('Classname')
. Если файл, который вы включаете, содержит код, т.е. вы используете его в процедурном порядке, то нет абсолютно никаких причин, которые require_once()
могут вам понадобиться; каждый раз, когда вы включаете файл, вы предполагаете, что выполняете вызов подпрограммы.
Так что какое-то время многие люди использовали этот class_exists()
метод для своих включений. Мне это не нравится, потому что это плохо, но у них были веские причины:require_once()
был довольно неэффективен перед некоторыми из более поздних версий PHP. Но это было исправлено, и я утверждаю, что дополнительный байт-код, который вам нужно будет скомпилировать для условного, и дополнительный вызов метода, намного перевесят любую внутреннюю проверку хеш-таблицы.
Теперь, признание: этот материал сложно проверить, потому что он занимает так мало времени выполнения.
Вот вопрос, о котором вы должны подумать: как правило, include в PHP дорогой, потому что каждый раз, когда интерпретатор нажимает на него, он должен переключаться обратно в режим разбора, генерировать коды операций и затем возвращаться назад. Если у вас есть 100+ включений, это определенно повлияет на производительность. Причина, по которой использование или не использование require_once является таким важным вопросом, заключается в том, что это усложняет жизнь для кэшей кода операции. Объяснение этому можно найти здесь, но то , что это сводится к тому, что:
Если во время синтаксического анализа вы точно знаете, какие включаемые файлы вам понадобятся на протяжении всего срока действия запроса, require()
то в самом начале и кэш кода операции будут обрабатывать все остальное за вас.
Если вы не используете кэш кода операции, вы находитесь в затруднительном положении. Включение всех ваших включений в один файл (не делайте этого во время разработки, только в производственной среде), безусловно, может помочь разобрать время, но это трудная задача, а также вам необходимо точно знать, что вы будете включать во время запрос.
Автозагрузка очень удобна, но медленна, потому что логика автозагрузки должна выполняться каждый раз, когда выполняется включение. На практике я обнаружил, что автозагрузка нескольких специализированных файлов для одного запроса не вызывает особых проблем, но вам не следует загружать все нужные вам файлы.
Если у вас есть, может быть, 10 включений (это очень обратная сторона расчета конверта), все это подделка не стоит: просто оптимизируйте запросы к вашей базе данных или что-то в этом роде.
define()
, require_once()
и defined()
все занимает около 1-2 микросекунд каждый на моей машине.
Мне стало любопытно и проверил ссылку Адама Бэкстрома на 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). ).
Можете ли вы дать нам какие-либо ссылки на эти методы кодирования, которые говорят, чтобы избежать этого? Насколько я понимаю, это совершенно не проблема . Я сам не смотрел на исходный код, но представляю, что единственная разница между include
и include_once
заключается в том, include_once
что это имя файла добавляется в массив и проверяет массив каждый раз. Было бы легко сохранить этот массив отсортированным, поэтому поиск по нему должен быть O (log n), и даже приложение среднего размера будет иметь только пару десятков включений.
Лучший способ сделать это - использовать объектно-ориентированный подход и использовать __autoload () .
__autoload()
не рекомендуется, и в будущем это может быть устаревшим, вы должны использовать spl_autoload_register(...)
эти дни ... PS2: не поймите меня неправильно, иногда я использую функцию автозагрузки; )
Это не использование функции, которая плоха. Это неверное понимание того, как и когда его использовать, в общей базе кода. Я просто добавлю немного больше контекста к этому, возможно, неправильно понятому понятию:
Люди не должны думать, что 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_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
Функция полезна , когда необходимо, но это не следует рассматривать как монолитное решение использовать везде для загрузки всех классов.
Вики 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
*_once()
Функции стат каждый родительский каталог для обеспечения файл , который вы в том числе не то же самое , как тот , который уже был включен. Это одна из причин замедления.
Я рекомендую использовать такой инструмент, как Siege для бенчмаркинга. Вы можете попробовать все предложенные методики и сравнить время отклика.
Больше на require_once()
это в Технологическом вашей вселенной .
Даже если require_once
и include_once
есть медленнее , чем require
и include
(или все , что может существовать альтернативы), мы говорим о маленьком уровне микро-оптимизации здесь. Ваше время гораздо лучше потрачено на оптимизацию плохо написанного цикла или запроса к базе данных, чем на беспокойство о чем-то подобном require_once
.
Теперь можно привести аргумент, говорящий о том, что это require_once
допускает плохую практику кодирования, потому что вам не нужно обращать внимание на то, чтобы ваши включения были чистыми и организованными, но это не имеет ничего общего с самой функцией , особенно с ее скоростью.
Очевидно, что автозагрузка лучше для чистоты кода и простоты обслуживания, но я хочу прояснить, что это никак не связано со скоростью .
Вы проверяете, используя include, альтернативу oli и __autoload (); и проверить это с чем-то вроде APC установленным .
Я сомневаюсь, что использование константы ускорит процесс.
Да, это немного дороже, чем обычные старые (). Я думаю, суть в том, что если вы можете сохранить свой код достаточно организованным, чтобы не дублировать включения, не используйте функции * _once (), так как это сэкономит вам несколько циклов.
Но использование функций _once () не убьет ваше приложение. В основном, просто не используйте это как оправдание, чтобы не организовывать ваши включения . В некоторых случаях его использование все еще неизбежно, и это не имеет большого значения.
Я думаю, что в документации PEAR есть рекомендации для require, require_once, include и include_once. Я следую этому руководству. Ваша заявка будет более понятной.
Это не имеет ничего общего со скоростью. Речь идет о изящной неудаче.
Если require_once () не работает, ваш скрипт готов. Больше ничего не обрабатывается. Если вы используете include_once (), остальная часть вашего сценария будет пытаться продолжить рендеринг, так что ваши пользователи потенциально не будут ничего удивляться тому, что не удалось в вашем сценарии.
Мое личное мнение заключается в том, что использование require_once (или include_once) является плохой практикой, потому что require_once проверяет вас, если вы уже включили этот файл, и подавляете ошибки двойных включенных файлов, приводящие к фатальным ошибкам (таким как двойное объявление функций / классов / и т. Д.) ,
Вы должны знать, если вам нужно включить файл.