Все, что я читал о лучших методах кодирования PHP, говорит, что не используйте require_once
из-за скорости.
Почему это?
Каков правильный / лучший способ сделать то же самое, что и require_once
? Если это имеет значение, я использую PHP5.
require_once
и include_once
требуют, чтобы система хранила журнал того, что уже было включено / требуется. Каждый вызов *_once
означает проверку этого журнала. Итак, есть определенная некоторая дополнительная работа, которая проводится там, но достаточно, чтобы снизить скорость всего приложения?
… Я действительно сомневаюсь в этом … Нет, если вы на действительно старом оборудовании или не делаете этого много .
Если вы делаете тысячи *_once
, вы можете сделать работу самостоятельно более легким способом. Для простых приложений просто убедитесь, что вы включили только один раз, но если вы все еще получаете ошибки переопределения, вы можете что-то вроде этого:
if (!defined('MyIncludeName')) { require('MyIncludeName'); define('MyIncludeName', 1); }
Я лично буду придерживаться утверждений *_once
но на глупом миллионном контрольном *_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 не стоит: просто оптимизируйте свои запросы к базе данных или что-то еще.
Мне стало любопытно и проверил связь Адама Бэкстрома с 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:
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); } ?>
Я не нашел большой разницы при выполнении этого с требованием 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 для всего пробега:
[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 () .
Функции *_once()
каждый родительский каталог, чтобы гарантировать, что файл, который вы включаете, не совпадает с тем, который уже был включен. Это часть причины замедления.
Я рекомендую использовать такой инструмент, как Siege для бенчмаркинга. Вы можете попробовать все предложенные методологии и сравнить время отклика.
Подробнее о require_once()
в Tech Your Universe .
Викия 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.. }
и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
не будет использоваться для любого заданного запроса, равно none, тогда require
обязательно должно быть в вашем основном классе. Наличие require_once
в каждом классе отдельно становится пустой тратой ресурсов.
Функция 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 проверяет вас, если вы уже включили этот файл и подавили ошибки двойных включенных файлов, что привело к фатальным ошибкам (например, дублирующее объявление функций / классов и т. Д.). ,
Вы должны знать, нужно ли включать файл.