Все, что я прочитал о лучших методах кодирования PHP, говорит, что не используйте require_once
из-за скорости.
Почему это?
Каков правильный/лучший способ сделать то же самое, что и require_once
? Если это имеет значение, я использую PHP5.
Все, что я прочитал о лучших методах кодирования PHP, говорит, что не используйте require_once
из-за скорости.
Почему это?
Каков правильный/лучший способ сделать то же самое, что и require_once
? Если это имеет значение, я использую PHP5.
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. Но это было исправлено, и я утверждаю, что дополнительный байт-код, который вы должны были бы скомпилировать для условного и дополнительного вызова метода, намного перевешивал бы любую внутреннюю проверку хэш-таблицы.
Теперь, вход: этот материал трудно тестировать, потому что на него приходится так мало времени выполнения.
Вот вопрос, о котором вы должны думать: включает, как правило, дорогое в PHP, потому что каждый раз, когда интерпретатор попадает на него, он должен переключиться обратно в режим синтаксического анализа, сгенерировать коды операций, а затем отскочить назад. Если у вас есть 100+, это, безусловно, повлияет на производительность. Причина, почему использование или отсутствие использования require_once является таким важным вопросом, состоит в том, что это затрудняет жизнь кэшам операций операций. Описание для этого можно найти здесь, но это все сводится к следующему:
Если во время разбора вы точно знаете, какие файлы вам понадобятся в течение всего срока службы запроса, require()
в самом начале, а кеш-код операции будет обрабатывать все остальное для вас.
Если вы не используете кеш opcode, вы находитесь в трудном положении. Вложение всех ваших включений в один файл (не делайте этого во время разработки, только в процессе производства), безусловно, может помочь разобрать время, но это больно, а также вам нужно точно знать, что вы будете включать в течение запрос.
Автозагрузка очень удобна, но медленна по той причине, что логика автозагрузки должна запускаться каждый раз, когда выполняется добавление. На практике я обнаружил, что автозагрузка нескольких специализированных файлов для одного запроса не вызывает слишком много проблем, но вы не должны автоматически загружать все файлы, которые вам понадобятся.
Если у вас, возможно, 10 включительно (это очень большая часть расчета конверта), все это wanking не стоит: просто оптимизируйте свои запросы к базе данных или что-то в этом роде.
Мне стало любопытно и проверил ссылку Адама 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).
Извините за длинный пост. Мне стало любопытно.
Можете ли вы дать нам какие-либо ссылки на эти методы кодирования, которые говорят, чтобы избежать этого? Насколько мне известно, это полная не-проблема. Я сам не смотрел исходный код, но я бы предположил, что единственная разница между include
и include_once
заключается в том, что include_once
добавляет это имя файла в массив и каждый раз проверяет массив. Было бы легко сохранить этот массив отсортированным, поэтому поиск по нему должен быть O (log n), и даже среднесрочное приложение будет содержать только несколько десятков.
Лучший способ сделать это - использовать объектно-ориентированный подход и использовать __ autoload().
Функции *_once()
ставят каждый родительский каталог, чтобы гарантировать, что файл, который вы включаете, не совпадает с тем, который уже был включен. Эта часть причины замедления.
Я рекомендую использовать инструмент, например Siege для бенчмаркинга. Вы можете попробовать все предложенные методологии и сравнить время отклика.
Подробнее о require_once()
в Технология вашей Вселенной.
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
Не работает функция, которая плоха. Это неправильное понимание того, как и когда использовать его, в общей базе кода. Я просто добавлю немного больше контекста к этому, возможно, неправильно понятому понятию:
Люди не должны думать, что 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
полезна при необходимости, но ее нельзя рассматривать как монолитное решение для использования везде для загрузки всех классов.
Даже если require_once
и include_once
медленнее, чем require
и include
(или любые альтернативы могут существовать), мы говорим о наименьшем уровне микро-оптимизации здесь. Ваше время намного лучше потратить на оптимизацию этого плохо написанного цикла или запроса к базе данных, чем беспокоиться о чем-то вроде require_once
.
Теперь можно сделать аргумент, говорящий, что require_once
допускает неправильные методы кодирования, потому что вам не нужно обращать внимание на то, чтобы ваши включения были чистыми и организованными, но это не имеет ничего общего с самой функцией и, особенно, его скорость.
Очевидно, что автозагрузка лучше для чистоты кода и простоты обслуживания, но я хочу дать понять, что это не имеет никакого отношения к скорости.
Вы тестируете, используя include, oli alternative и __autoload(); и протестируйте его с помощью что-то вроде APC. Я сомневаюсь, что использование постоянной скорости ускорит процесс.
Да, он немного дороже, чем простой ol 'require(). Я думаю, что дело в том, что вы можете сохранить свой код достаточно организованным, чтобы не включать в него, не используйте функции * _once(), так как это сэкономит вам несколько циклов.
Но использование функций _once() не собирается убивать ваше приложение. В принципе, только не используют его в качестве предлога для того, чтобы не включать ваши. В некоторых случаях использование его по-прежнему неизбежно, и это не имеет большого значения.
Я думаю, что в документации PEAR есть рекомендация require, require_once, include и include_once. Я следую этому руководству. Ваше приложение будет более понятным.
Это не имеет никакого отношения к скорости. Это о неудачной изящности.
Если require_once() терпит неудачу, выполняется script. Ничто другое не обрабатывается. Если вы используете include_once(), оставшаяся часть вашего script будет пытаться продолжить рендеринг, поэтому ваши пользователи потенциально не будут иметь ничего общего с тем, что потерпело неудачу в вашем script.
Мое личное мнение заключается в том, что использование require_once (или include_once) - это плохая практика, потому что require_once проверяет вас, если вы уже включили этот файл и подавили ошибки двойных включенных файлов, что привело к фатальным ошибкам (например, дублирующее объявление функций/классов/и др.).
Вы должны знать, нужно ли вам включать файл.