Все, що я читав про кращі практики кодування 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. Але це було виправлено, і я стверджую, що додатковий байт-код, який вам доведеться скласти для умовного, та виклик додаткового методу, значно перевершить будь-яку внутрішню перевірку хештебля.
Тепер, визнання: цей матеріал важко перевірити, оскільки на нього припадає так мало часу на виконання.
Ось питання, над яким ви повинні задуматися: включає, як правило, дорогі PHP, тому що кожного разу, коли інтерпретатор потрапляє на нього, він повинен переключитися в режим розбору, генерувати опкоди, а потім стрибати назад. Якщо у вас є 100+ включає, це, безумовно, матиме ефективність. Причина того, що використовувати або не використовувати Requ_once є таким важливим питанням, це тому, що це ускладнює життя кешів опкодів. Пояснення цьому можна знайти тут, але то , що це зводиться до того, що:
Якщо під час розбору ви точно знаєте, що включає файли, які вам знадобляться протягом усього життя запиту, require()
ті , що знаходяться на самому початку, та кеш-код опкоду будуть обробляти все, що вам потрібно.
Якщо ви не використовуєте кеш-код опкоду, ви знаходитесь у важкому місці. Об’єднання всіх ваших включень в один файл (не робіть цього під час розробки, тільки у виробництві), безумовно, може допомогти розібратися в часі, але це болісно, а також вам потрібно точно знати, що ви будете включати під час запит.
Автозавантаження дуже зручно, але повільно, з тієї причини, що логіку автозавантаження потрібно запускати щоразу, коли робиться включення. На практиці я виявив, що автозавантаження декількох спеціалізованих файлів за один запит не викликає надто великих проблем, але ви не повинні автоматично завантажувати всі потрібні файли.
Якщо у вас, можливо, 10 включень (це дуже зворотний бік обчислення конвертів), все це не хочеться: просто оптимізуйте запити до вашої бази даних чи щось.
define()
, require_once()
і defined()
всі займають приблизно 1-2 мікросекунди на кожній моїй машині.
Мені стало цікаво і перевірив посилання Адама Бекстрома на Tech Your Universe . У цій статті описано одну з причин, за якою потрібно використовувати замість Requ_once. Однак їхні претензії не дотрималися мого аналізу. Мені було б цікаво подивитися, де я, можливо, неправильно проаналізував рішення. Я використовував PHP 5.2.0 для порівняння.
Я почав, створивши 100 файлів заголовків, які використовували Requ_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
Таким чином я міг легко обмінятися між використанням Requ_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 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);
?>
Я протиставляв заголовки Requ_once із заголовками вимагають, які використовували файл заголовка таким чином:
<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
require "../phpperf/common_hdr.php";
define('CommonHdr', 1);
}
?>
Я не знайшов великої різниці, коли запускав цю програму з вимогою по відношенню до вимоги вимагаю Насправді, мої початкові тести, здавалося, означали, що Requ_once був трохи швидшим, але я не обов'язково вірю в це. Я повторив експеримент із 10000 вхідними файлами. Тут я побачив стійку різницю. Я проводив тест кілька разів, результати є близькими, але використовуючи Requ_once використовує в середньому 30,8 магістральних користувачів і 72,6 системних джиффів; використовуючи потребує використання в середньому 39,4 користувальницьких джифф і 72.0 системних джиффі. Тому виявляється, що навантаження дещо нижча за допомогою Requ_once. Однак час настінного годинника трохи збільшений. Для виклику 10 000 посилань на вимогу в середньому використовується 10,15 секунди, а 10 000 - в середньому 9,84 секунди.
Наступний крок - розглянути ці відмінності. Я використовував strace для аналізу системних викликів, які здійснюються.
Перед відкриттям файлу з Requ_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
Технологія Вашого Всесвіту означає, що requ_once повинен здійснювати більше дзвінків lstat64. Однак вони обидва роблять однакову кількість дзвінків lstat64. Можливо, різниця полягає в тому, що я не запускаю APC для оптимізації коду вище. Однак далі я порівняв вихід страйсу на всі прогони:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190709 strace_1000r.out
210707 strace_1000ro.out
401416 total
Ефективно існує приблизно ще два системні виклики на файл заголовка при використанні Requ_once. Одна з різниць полягає в тому, що Requ_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", яка стверджує, що вона зменшує кількість системних викликів, здійснених за допомогою call_once та включає виклики_once (див. Документацію PHP ).
Чи можете ви надати нам будь-які посилання на ці методи кодування, які говорять про те, щоб цього не робити? Що стосується мене, то це повна проблема . Я не дивився на вихідний код сам, але я б собі уявити , що єдина відмінність між include
і в include_once
тому , що include_once
додає , що ім'я файлу в масив і перевіряє через масив кожен раз. Буде легко сортувати цей масив відсортованим, тому пошук по ньому повинен бути O (log n), і навіть додаток середнього розміру повинен мати лише пару десятків.
Кращий спосіб робити речі - це використовувати об'єктно-орієнтований підхід і використовувати __autoload () .
__autoload()
не рекомендується, і це може бути застарілим у майбутньому, ви повинні використовувати spl_autoload_register(...)
ці дні ... PS2: не зрозумійте мене неправильно, я іноді використовую функцію автозавантаження; )
Це не використання функції, яка погана. Це неправильне розуміння того, як і коли його використовувати, в загальній кодовій базі. Я просто додам трохи більше контексту до цього, можливо, неправильно зрозумілого поняття:
Люди не повинні думати, що Requ_once - це повільна функція. Ви повинні так чи інакше включити свій код. require_once()
проти require()
швидкості - це не проблема. Йдеться про ефективність, що перешкоджає застереженням, які можуть призвести до сліпого використання. Якщо використовувати його широко, не враховуючи контекст, це може призвести до величезної трати пам’яті або марного коду.
Я бачив, що це дуже погано - це коли величезні монолітні рамки використовують require_once()
усі неправильні способи, особливо в складних об'єктно-орієнтованих (OO) середовищах.
Візьмемо приклад використання 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");
Використання Requ_once () технічно впливає на продуктивність для виконання цієї функції 14 разів, крім того, щоб розібрати ці непотрібні рядки. Маючи лише 10 інших високо використовуваних класів із подібною проблемою, він може нараховувати 100+ рядків такого досить безглуздого повторюваного коду.
З цим, напевно require("includes/helpers.php");
, замість цього варто скористатись у завантажувальній програмі вашої програми чи структури. Але оскільки все відносно, все залежить від того, helpers
чи варто важити в порівнянні з частотою використання класу 15-100 рядків require_once()
. Але якщо ймовірність не використовувати helpers
файл у будь-якому заданому запиті відсутня, то require
натомість слід бути у вашому головному класі. Перебування require_once
в кожному класі окремо стає марною тратою ресурсів.
require_once
Функція корисна , коли необхідно, але це не слід розглядати як монолітне рішення використовувати всюди для завантаження всіх класів.
Вікі PEAR2 (коли вона існувала) використовувалась для перерахування вагомих причин відмови від усіх вимог / включення директив на користь автозавантаження , принаймні щодо коду бібліотеки. Вони прив'язують вас до жорстких структур каталогів, коли альтернативні моделі упаковки, такі як phar, знаходяться на горизонті.
Оновлення: Оскільки веб-архівована версія вікі є некрасивою для очей, я скопіював найважливіші причини нижче:
- include_path необхідний для використання пакету (PEAR). Це ускладнює поєднання пакета PEAR в іншій програмі з його власним include_path, створення єдиного файлу, що містить необхідні класи, переміщення пакета PEAR в архів phar без великої модифікації вихідного коду.
- коли верхній рівень Requ_once змішується з умовним Requ_once, це може призвести до неможливості кеш-коду кешами опкодів, такими як APC, які будуть в комплекті з PHP 6.
- відносно Requ_once вимагає, щоб include_path вже був налаштований на правильне значення, що робить неможливим використання пакета без належного include_path
Ці *_once()
функції стат кожен батьківський каталог для забезпечення файл , який ви в тому числі не те ж саме , як той , який вже був включений. Це частина причини сповільнення.
Я рекомендую використовувати такий інструмент, як Siege, для проведення бенчмаркінгу. Ви можете спробувати всі запропоновані методології та порівняти часи відповідей.
Більше про те, що require_once()
знаходиться у Tech Your Universe .
Навіть якщо require_once
і include_once
є повільніше , ніж require
та include
(або все , що може існувати альтернативи), ми говоримо про маленького рівні мікро-оптимізації тут. Ваш час набагато краще витратити на оптимізацію цього погано написаного циклу чи запиту до бази даних, ніж на занепокоєння з приводу чогось подібного require_once
.
Тепер, можна зробити аргумент, що говорить про require_once
погані практики кодування, оскільки вам не потрібно звертати увагу на те, щоб ваші включення були чистими та організованими, але це не має нічого спільного з самою функцією особливо не з її швидкістю.
Очевидно, що автоматичне завантаження краще заради чистоти коду та простоти обслуговування, але я хочу дати зрозуміти, що це не має нічого спільного зі швидкістю .
Ви протестуєте, використовуючи альтернативу oli, альтернативу oli та __autoload (); і протестуйте його чимось на зразок APC встановивши .
Сумніваюсь, що використання константи пришвидшить справи.
Так, це трохи дорожче, ніж звичайне ol 'вимагає (). Я думаю, що справа в тому, якщо ви можете тримати свій код достатньо організованим, щоб не дублювати включення, не використовуйте функції * _once (), оскільки це заощадить певні цикли.
Але використання функцій _once () не вбиває вашу програму. В основному, просто не використовуйте це як привід для того, щоб не потрібно організовувати свої включення . У деяких випадках використання його все ще неминуче, і це не є великою справою.
Я думаю, що в документації PEAR є рекомендація щодо вимагати, Requ_once, включати і include_once. Я дотримуюся цієї настанови. Ваша заявка була б більш зрозумілою.
Це не має нічого спільного зі швидкістю. Йдеться про те, що не вдасться витончено.
Якщо поправка Requ_once () не вдається, ваш сценарій буде виконано. Більше нічого не обробляється. Якщо ви використовуєте include_once (), решта вашого сценарію намагатиметься надалі рендерірувати, тому потенційно ваші користувачі не будуть мудрішими щодо того, що у вашому сценарії не вдалося.
Моя особиста думка полягає в тому, що використання Requ_once (або include_once) є поганою практикою, тому що Requ_once перевіряє для вас, якщо ви вже включили цей файл і пригнічуєте помилки подвійних файлів, що включаються, що призводять до фатальних помилок (наприклад, дублювання декларацій функцій / класів / тощо). .
Ви повинні знати, чи потрібно додати файл.