Wszystko, co czytałem o lepszych praktykach kodowania PHP, ciągle mówi, że nie używaj require_once
powodu szybkości.
Dlaczego to?
Jaki jest właściwy / lepszy sposób zrobienia tego samego, co require_once
? Jeśli ma to znaczenie, używam PHP 5.
Wszystko, co czytałem o lepszych praktykach kodowania PHP, ciągle mówi, że nie używaj require_once
powodu szybkości.
Dlaczego to?
Jaki jest właściwy / lepszy sposób zrobienia tego samego, co require_once
? Jeśli ma to znaczenie, używam PHP 5.
Odpowiedzi:
require_once
i include_once
oba wymagają, aby system prowadził dziennik tego, co już zostało uwzględnione / wymagane. Każde *_once
połączenie oznacza sprawdzenie tego dziennika. Więc na pewno niektóre dodatkowa praca wykonywana tam, ale na tyle na niekorzyść szybkości całej aplikacji?
... Naprawdę w to wątpię ... Nie, chyba że jesteś na naprawdę starym sprzęcie lub często to robisz .
Jeśli są robi tysiące *_once
, można zrobić samemu w jaśniejszy sposób. W przypadku prostych aplikacji, po prostu upewniając się wcześniej tylko raz włączone to powinno wystarczyć, ale jeśli nadal się błędy przedefiniować, można było coś takiego:
if (!defined('MyIncludeName')) {
require('MyIncludeName');
define('MyIncludeName', 1);
}
Osobiście będę trzymał się *_once
stwierdzeń, ale na głupim benchmarku z milionem przebiegów widać różnicę między nimi:
php hhvm
if defined 0.18587779998779 0.046600103378296
require_once 1.2219581604004 3.2908599376678
10-100 × wolniej z require_once
i ciekawe, że require_once
wydaje się wolniej hhvm
. Ponownie, ma to znaczenie tylko dla twojego kodu, jeśli wykonujesz *_once
tysiące razy.
<?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.
Wątek ten sprawia, że się wzdrygam, ponieważ zostało już „opublikowane rozwiązanie” i jest ono pod każdym względem błędne. Wymieńmy:
W PHP definicje są naprawdę drogie. Możesz to sprawdzić lub przetestować samodzielnie, ale jedynym skutecznym sposobem zdefiniowania stałej globalnej w PHP jest użycie rozszerzenia. (Stałe klasowe są w rzeczywistości całkiem przyzwoite, jeśli chodzi o wydajność, ale jest to kwestia dyskusyjna, ponieważ 2)
Jeśli używasz require_once()
odpowiednio, to znaczy włączasz klasy, nie potrzebujesz nawet definicji; po prostu sprawdź, czy class_exists('Classname')
. Jeśli plik, który dołączasz, zawiera kod, tj. Używasz go w sposób proceduralny, nie ma absolutnie żadnego powodu, który require_once()
powinien być dla Ciebie potrzebny; za każdym razem, gdy dołączasz plik, zakładasz, że wywołuje podprogram.
Tak więc przez jakiś czas wiele osób używało tej class_exists()
metody do swoich inkluzji. Nie podoba mi się to, ponieważ jest zepsuty, ale mieli dobry powód, aby: require_once()
był dość nieefektywny przed niektórymi nowszymi wersjami PHP. Ale to zostało naprawione i twierdzę, że dodatkowy kod bajtowy, który musiałbyś skompilować dla warunku, i dodatkowe wywołanie metody, zdecydowanie przeważałyby nad jakimkolwiek wewnętrznym sprawdzaniem z tablicą haszującą.
A teraz przyznaję: to jest trudne do przetestowania, ponieważ zajmuje tak mało czasu wykonania.
Oto pytanie, o którym powinieneś pomyśleć: obejmuje z reguły są drogie w PHP, ponieważ za każdym razem, gdy interpreter trafia w jeden, musi przełączyć się z powrotem w tryb parsowania, wygenerować kody operacyjne, a następnie skoczyć z powrotem. Jeśli masz ponad 100 elementów, z pewnością będzie to miało wpływ na wydajność. Powodem, dla którego używanie lub nieużywanie require_once jest tak ważne pytanie, jest to, że utrudnia życie buforom opcode. Wyjaśnienie tego można znaleźć tutaj, ale to, co sprowadza się do tego, że:
Jeśli w czasie parsowania wiesz dokładnie, jakich plików nagłówkowych będziesz potrzebować przez cały czas trwania żądania, require()
te na samym początku i pamięć podręczna kodu operacji zajmie się wszystkim innym za Ciebie.
Jeśli nie korzystasz z pamięci podręcznej kodu operacyjnego, jesteś w ciężkim miejscu. Umieszczanie wszystkich dołączeń w jednym pliku (nie rób tego podczas programowania, tylko w środowisku produkcyjnym) może z pewnością pomóc przeanalizować czas, ale jest to uciążliwe, a ponadto musisz dokładnie wiedzieć, co będziesz uwzględniać podczas żądanie.
Automatyczne ładowanie jest bardzo wygodne, ale powolne, ponieważ logika automatycznego ładowania musi być uruchamiana za każdym razem, gdy jest wykonywane dołączanie. W praktyce odkryłem, że automatyczne ładowanie kilku wyspecjalizowanych plików dla jednego żądania nie powoduje zbyt dużego problemu, ale nie powinno się automatycznie ładować wszystkich potrzebnych plików.
Jeśli masz może 10 włączeń (to bardzo koniec kalkulacji obwiedni), całe to wankowanie nie jest tego warte: po prostu zoptymalizuj zapytania do bazy danych lub coś takiego.
define()
, require_once()
a defined()
każde z nich zajmuje około 1-2 mikrosekund na moim komputerze.
Zaciekawiło mnie to i sprawdziłem link Adama Backstroma do Tech Your Universe . W tym artykule opisano jeden z powodów, dla których należy użyć wymagania zamiast require_once. Jednak ich twierdzenia nie potwierdziły mojej analizy. Chciałbym zobaczyć, gdzie mogłem źle przeanalizować rozwiązanie. Do porównań użyłem PHP 5.2.0.
Zacząłem od stworzenia 100 plików nagłówkowych, które wykorzystywały require_once do dołączenia innego pliku nagłówkowego. Każdy z tych plików wyglądał mniej więcej tak:
<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";
?>
Stworzyłem je za pomocą szybkiego hacka Bash:
for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
echo "<?php
// $i" > $i
cat helper.php >> $i;
done
W ten sposób mogłem łatwo przełączać się między użyciem require_once i wymaganiem przy dołączaniu plików nagłówkowych. Następnie utworzyłem plik app.php, aby załadować sto plików. Wyglądało to tak:
<?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);
?>
Kontrastowałem nagłówki require_once z nagłówkami wymagającymi, które używały pliku nagłówkowego wyglądającego tak:
<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
require "../phpperf/common_hdr.php";
define('CommonHdr', 1);
}
?>
Nie znalazłem dużej różnicy podczas uruchamiania tego z wymaganiem a require_once. W rzeczywistości moje wstępne testy wydawały się sugerować, że require_once była nieco szybsza, ale niekoniecznie w to wierzę. Powtórzyłem eksperyment z 10000 plików wejściowych. Tutaj widziałem stałą różnicę. Przeprowadziłem test wiele razy, wyniki są bliskie, ale użycie require_once używa średnio 30,8 jiffies użytkownika i 72,6 systemowych jiffies; używanie require używa średnio 39,4 jiffies użytkownika i 72,0 systemowych jiffies. Dlatego wydaje się, że obciążenie jest nieco mniejsze przy użyciu require_once. Jednak zegar ścienny nieco się wydłuża. 10 000 połączeń require_once zajmuje średnio 10,15 sekundy, a 10 000 połączeń wymaga średnio 9,84 sekundy.
Następnym krokiem jest przyjrzenie się tym różnicom. Użyłem strace do analizy wywołań systemowych, które są wykonywane.
Przed otwarciem pliku z require_once wykonywane są następujące wywołania systemowe:
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
Kontrastuje to z wymaganiami:
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 sugeruje, że require_once powinno wykonywać więcej wywołań lstat64. Jednak oba wykonują taką samą liczbę wywołań lstat64. Być może różnica polega na tym, że nie używam APC do optymalizacji powyższego kodu. Jednak następnie porównałem wyniki strace dla całych biegów:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190709 strace_1000r.out
210707 strace_1000ro.out
401416 total
W efekcie w przypadku korzystania z require_once istnieje około dwóch dodatkowych wywołań systemowych na plik nagłówkowy. Jedna różnica polega na tym, że require_once ma dodatkowe wywołanie funkcji time ():
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008
Drugie wywołanie systemowe to getcwd ():
[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004
Nazywa się to, ponieważ zdecydowałem się na ścieżkę względną, do której odwołują się pliki hdrXXX. Jeśli uczynię to odniesieniem bezwzględnym, jedyną różnicą jest wywołanie dodatkowego czasu (NULL) wykonane w kodzie:
[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
Wydaje się to sugerować, że można zmniejszyć liczbę wywołań systemowych, używając ścieżek bezwzględnych zamiast ścieżek względnych. Jedyną różnicą poza tym jest wywołania time (NULL), które wydają się być używane do instrumentowania kodu w celu porównania tego, co jest szybsze.
Inną uwagą jest to, że pakiet optymalizacyjny APC ma opcję o nazwie „apc.include_once_override”, która twierdzi, że zmniejsza liczbę wywołań systemowych wykonywanych przez wywołania require_once i include_once (zobacz dokumentację PHP ).
Czy możesz podać nam linki do tych praktyk kodowania, które mówią, że należy tego unikać? Jeśli o mnie chodzi, to kompletny nie problem . Sam nie przeglądałem kodu źródłowego, ale wyobrażam sobie, że jedyną różnicą między include
i include_once
jest to, że include_once
dodaje tę nazwę pliku do tablicy i sprawdza tablicę za każdym razem. Łatwo byłoby posortować tę tablicę, więc przeszukiwanie jej powinno mieć wartość O (log n), a nawet średnio-duża aplikacja miałaby tylko kilka tuzinów dołączeń.
Lepszym sposobem na robienie rzeczy jest użycie podejścia obiektowego i użycie __autoload () .
__autoload()
jest odradzane i może być przestarzałe w przyszłości, powinieneś używać spl_autoload_register(...)
tych dni ... PS2: nie zrozum mnie źle, czasami używam funkcji automatycznego ładowania; )
Nie używa złej funkcji. Jest to niepoprawne zrozumienie, jak i kiedy go używać w ogólnej bazie kodu. Dodam tylko trochę więcej kontekstu do tego prawdopodobnie niezrozumianego pojęcia:
Ludzie nie powinni myśleć, że require_once to powolna funkcja. Musisz dołączyć swój kod w ten czy inny sposób. require_once()
vs. require()
prędkość nie jest problemem. Chodzi o wydajność utrudniającą zastrzeżenia, które mogą skutkować używaniem go na ślepo. Jeśli jest używany szeroko bez uwzględnienia kontekstu, może prowadzić do ogromnych strat pamięci lub marnotrawstwa kodu.
To, co widziałem, jest naprawdę złe, gdy ogromne monolityczne frameworki używają require_once()
na wszystkie niewłaściwe sposoby, szczególnie w złożonym środowisku obiektowym (OO).
Weź przykład użycia require_once()
na początku każdej klasy, jak widać w wielu bibliotekach:
require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
// User functions
}
Tak więc User
klasa została zaprojektowana do używania wszystkich trzech innych klas. Słusznie!
Ale co teraz, jeśli użytkownik przegląda witrynę i nawet nie jest zalogowany, a struktura się ładuje: require_once("includes/user.php");
dla każdego żądania.
Zawiera 1 + 3 niepotrzebnych klas, których nigdy nie użyje podczas tego konkretnego żądania. W ten sposób rozdęte platformy zużywają 40 MB na żądanie, zamiast 5 MB lub mniej.
Innym sposobem, w jaki może być nadużywany, jest sytuacja, gdy klasa jest ponownie używana przez wielu innych! Załóżmy, że masz około 50 klas korzystających z helper
funkcji. Aby upewnić się, że helpers
są dostępne dla tych klas po ich załadowaniu, otrzymujesz:
require_once("includes/helpers.php");
class MyClass{
// Helper::functions(); // etc..
}
Nie ma tu nic złego per se. Jeśli jednak jedno żądanie strony zawiera 15 podobnych klas. Biegasz require_once
15 razy lub dla ładnego obrazu:
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");
Użycie require_once () technicznie wpływa na wydajność działania tej funkcji 14 razy, oprócz konieczności analizowania tych niepotrzebnych wierszy. Mając zaledwie 10 innych, często używanych klas, które mają podobny problem, może to stanowić ponad 100 wierszy takiego raczej bezcelowego, powtarzalnego kodu.
W związku z tym prawdopodobnie warto użyć require("includes/helpers.php");
zamiast tego na ładowaniu aplikacji lub frameworku. Ale ponieważ wszystko jest względne, wszystko zależy od tego, czy waga w porównaniu z częstotliwością użytkowania helpers
klasy jest warta zaoszczędzenia 15-100 linii require_once()
. Ale jeśli prawdopodobieństwo nieużywania helpers
pliku w jakimkolwiek żądaniu jest zerowe, to require
zdecydowanie powinno być zamiast tego w twojej głównej klasie. Posiadanie require_once
osobno w każdej klasie staje się marnotrawstwem zasobów.
require_once
Funkcja jest przydatna, gdy jest to konieczne, ale nie powinny być traktowane jako monolityczny rozwiązanie wszędzie używać do ładowania wszystkich klas.
Wiki PEAR2 (o ile istniało) podawało dobre powody do porzucenia wszystkich dyrektyw require / include na rzecz automatycznego ładowania , przynajmniej dla kodu biblioteki. To przywiązuje Cię do sztywnych struktur katalogów, gdy na horyzoncie pojawiają się alternatywne modele pakowania, takie jak phar .
Aktualizacja: ponieważ wersja wiki zarchiwizowana w Internecie jest niesamowicie brzydka, skopiowałem poniżej najważniejsze powody:
- include_path jest wymagana, aby użyć pakietu (PEAR). Utrudnia to pakowanie pakietu PEAR do innej aplikacji z własną ścieżką include_path, tworzenie pojedynczego pliku zawierającego potrzebne klasy, przenoszenie pakietu PEAR do archiwum phar bez rozległej modyfikacji kodu źródłowego.
- Gdy wymieszanie require_once najwyższego poziomu z warunkowym require_once może spowodować, że kod nie będzie można zapisać w pamięci podręcznej kodu operacji, takiej jak APC, która będzie dołączona do PHP 6.
- względne require_once wymaga, aby parametr include_path był już ustawiony na poprawną wartość, co uniemożliwia użycie pakietu bez odpowiedniej ścieżki include_path
*_once()
Funkcje stat każdym katalogu nadrzędnego, aby upewnić się, że plik jesteś tym nie jest taki sam jak ten, który jest już zawarty. To jest jeden z powodów spowolnienia.
Zalecam użycie narzędzia takiego jak Siege do testów porównawczych. Możesz wypróbować wszystkie sugerowane metodologie i porównać czasy odpowiedzi.
Więcej na require_once()
jest w Tech waszego wszechświata .
Nawet jeśli require_once
i include_once
są wolniejsze niż require
i include
(lub jakiekolwiek alternatywy mogą istnieć), mówimy tutaj o najmniejszym poziomie mikro-optymalizacji. Znacznie lepiej poświęcić czas na optymalizację tej źle napisanej pętli lub zapytania do bazy danych niż martwienie się o coś takiego require_once
.
Teraz można by argumentować, mówiąc, że require_once
dopuszcza to złe praktyki kodowania, ponieważ nie musisz zwracać uwagi na utrzymanie czystości i porządku w dołączeniach, ale nie ma to nic wspólnego z samą funkcją , a zwłaszcza z jej szybkością.
Oczywiście automatyczne ładowanie jest lepsze ze względu na czystość kodu i łatwość konserwacji, ale chcę wyjaśnić, że nie ma to nic wspólnego z szybkością .
Testujesz, używając funkcji include, alternatywy oli i __autoload (); i przetestuj go z zainstalowanym czymś takim jak APC .
Wątpię, by używanie stałej przyspieszyło sprawę.
Tak, jest to nieco droższe niż zwykłe wymagania (). Myślę, że jeśli możesz utrzymać swój kod na tyle zorganizowany, aby nie powielać dołączeń, nie używaj funkcji * _once (), ponieważ pozwoli to zaoszczędzić kilka cykli.
Ale użycie funkcji _once () nie zabije twojej aplikacji. Zasadniczo nie używaj tego jako wymówki, aby nie organizować swoich dołączeń . W niektórych przypadkach używanie go jest nadal nieuniknione i nie jest to wielka sprawa.
Myślę, że w dokumentacji PEAR jest zalecenie dla require, require_once, include i include_once. Przestrzegam tych wytycznych. Twoja aplikacja byłaby bardziej przejrzysta.
Nie ma to nic wspólnego z szybkością. Chodzi o to, żeby wdzięcznie zawieść.
Jeśli require_once () zawiedzie, Twój skrypt jest gotowy. Nic więcej nie jest przetwarzane. Jeśli użyjesz funkcji include_once (), reszta twojego skryptu będzie próbowała kontynuować renderowanie, więc Twoi użytkownicy potencjalnie nie byliby mądrzejsi w przypadku czegoś, co zawiodło w twoim skrypcie.
Osobiście uważam, że użycie require_once (lub include_once) jest złą praktyką, ponieważ require_once sprawdza za ciebie, czy już dołączyłeś ten plik i pomija błędy podwójnie dołączonych plików, które powodują błędy krytyczne (takie jak zduplikowana deklaracja funkcji / klas / itp.) .
Powinieneś wiedzieć, czy musisz dołączyć plik.