PHP: Jak napisać wysokowydajny skrypt
Agencja internetowa » Wiadomości cyfrowe » PHP: Jak napisać wysokowydajny skrypt

PHP: Jak napisać wysokowydajny skrypt

PHP może być językiem skryptowym i jest krytykowany za powolność, ale pozostaje jednym z najczęściej używanych języków serwerów WWW. Z biegiem czasu główne firmy internetowe korzystające z PHP starały się zoptymalizować silnik. Wprowadzenie pojęcia Śmieciarz w wersji 5.3 był znaczącym postępem. Ale jest na to wiele sposobów„zoptymalizuj wydajność skryptu.

Stosowane głównie w kontekście prostego generowania stron, optymalizacje te, choć mogą przynieść pewien zysk, nie są wystarczająco widoczne dla użytkownika. Optymalizacja baz danych, serwerów WWW z wykorzystaniem Nginx, optymalizacja silników wraz z pojawieniem się HHVM czy nawet HippyVM pozwoli Ci po prostu przyspieszyć renderowanie Twoich stron i w prostszy sposób zoptymalizować czas odpowiedzi na Twoje zapytania . Mimo to czasami opracowujemy skrypty PHP w celu tworzenia ciężkie leczenielub serwery internetowe nie mogą nic zrobić.

W tym poście opiszę szczegółowo trzy obszary optymalizacji który niedawno zastosowałem do skryptu, który miał przetwarzać pliki CSV lub XLS zawierające dużą ilość informacji. Ilość używanej pamięci osiągnęła 1 GB bez obaw i mogła trwać dłużej niż 1/2 godziny.

Zanim przedstawię ci trzy pojęcia PHP, które mogą ci na to pozwolićzoptymalizować czas realizacji a także ilość zajętej pamięci, wiedz, że zanim zaczniesz narzekać na język X, algorytm twojego skryptu jest odpowiedzialny za znaczną część twojego przetwarzania. C++ może być nieskończenie szybszy niż PHP, ale jeśli twoje algorytmy C++ są złe, nie rozwiąże to natychmiast twoich problemów.

Zwolnij przydział jego zmiennych, ogranicz zużycie pamięci

Przed wydaniem PHP 5.3 problemem PHP było nadmierne zużycie pamięci. Poza tym często słyszałem, że pamięci skryptu PHP nigdy nie da się zmniejszyć… Jeśli to była prawda przez jakiś czas, to na szczęście już nie jest. To zarządzanie wykorzystuje pojęcie Garbage Collector, które zobaczymy nieco dalej.

Dobry nawyk stracony...

W niektórych językach było to wymagane. W PHP pojęcie to zostało całkowicie zapomniane! Ta mała natywna funkcja unset() ma na celupotężna użyteczność. Jest odpowiednikiem funkcji free() w C++ i pozwala na to zwolnić a zatem z natychmiast zwolnić pamięć używany przez zmienną. Zmienna jest całkowicie zniszczona. To początkowo uwalnia PHP od nieużywanych zmiennych. Ma to inny interes, pomagając nam lepiej ustrukturyzować nasze algorytmy. Oczywiście, kiedy metoda się kończy, zmienne są automatycznie zwalniane, ale zobaczysz poniżej, że pozwala to zoptymalizować czas, jaki „garbage collector” spędza na pracy lub po prostu wykonywaniu swojej pracy w przypadku dezaktywacji. Istnieje zatem również zysk pod względem szybkości. I uwierz mi, GC zużywa dużo zasobów, aby zwolnić pamięć.

Jak powiedziałem w poprzednim akapicie, na końcu funkcji lub metody PHP usuwa nieużywane zmienne. Ale w przypadku skryptu przetwarzającego dane masowe możliwe jest, aby jedna funkcja administrowała innymi. Tak jak w niektórych językach, jeśli jest to główna funkcja, prawdopodobnie będziesz miał tendencję do przechowywania zmiennych z powrotem z funkcji przed przekazaniem ich do innych funkcji. Możemy szybko skończyć z dużą ilością danych. Gdy tylko przestanie być używany, nie ma sensu go „nosić”, zwalniamy go.

Ostatnio, pisząc skrypt, w którym rozpakowywałem cały plik większy niż 15 MB, po skorzystaniu z niego usunąłem go i pozwoliłem zyskać pamięć !

Zrozumienie Garbage Collectora

Podczas badania zarządzania pamięcią natknąłem się na artykuł kolegi. W tym artykule, który jest już nieco stary, Pascal Martin wyjaśnia nowość, której już nie ma, Garbage Collector.

Co to jest Garbage Collector?

Dla tych, którzy go nie znają lub już o nim słyszeli, ale nigdy nie mieli okazji go używać, Garbage Collector, co po francusku oznacza „zbieranie okruchów”, funkcjonalność na niskim poziomie który w przedziale X zatrzymuje uruchomiony proces i wykonuje skanowanie pamięci przydzielonej przez skrypt w celu wykrycia, które zmienne nie są już dostępne w celu ich usunięcia.

Nie rozumiem, wcześniej powiedziano mi, że na końcu metody PHP niszczy zmienne.
To nie do końca prawda. W taki sam sposób jak C lub C++ (rodzic PHP), na końcu metody PHP kończy działanie i traci referencję, którą miał na obiekcie. W C istnieje tylko pojęcie wskaźnika, w C++ pojęcia wskaźnika i odniesienia współistnieją.

Po utracie odniesienia do obiektu i uruchomieniu Garbage Collector, ten ostatni stwierdzi, że zmienna nie jest już dostępna i zniszczy ją.

Ta koncepcja jest bardzo obecna w JAVA, a także w innych nowszych językach, a co za tym idzie, na wyższym poziomie.

Co przyniósł śmieciarz?

GC zapewniało znaczną elastyczność programistyczną. Oczywiste jest, że programowanie w C jest bardziej techniczne niż w Javie, PHP czy innych. Mimo to niektóre aspekty są teraz zapomniane, takie jak zarządzanie pamięcią i to nasz kod czasami współczuje. Jeśli zapewniało to elastyczność w naszym rozwoju, spowalniało również realizację naszych programów.

Dzięki tym narzędziom nie możemy zapominać, że zawsze możemy w jakiś sposób kontrolować pamięć. Kwestia woli, czy konieczności…

Na koniec uważam, że tego typu narzędzia są bardzo praktyczne, ale nie mogą zastąpić instrukcji wydawanych przez programistów. Ze swojej strony uważam, że pojęcie C++ shared_ptr jest o wiele bardziej interesująca i może zaoferować w przyszłości niewiarygodny wzrost wydajności, jeśli Zend zdecyduje się użyć tej metody w niektórych przypadkach.

A w PHPie?

Od PHP 5.3 dodano cztery funkcje.

  • gc_enable
  • gc_enabled
  • gc_disable
  • gc_collect_cykle
    Zostawiam ci czas na przeczytanie dokumentacji, ale żeby być szybkim, pozwalają ci aktywować GC, wiedzieć, czy jest aktywny, dezaktywować go i ręcznie uruchomić kolekcję.

Pascal Martin opublikował w poście, do którego linkowałem powyżej, skrypt, w którym wykonuje test baterii. W raporcie z testów wyraźnie widać, że wersja bez GC osiąga ogromne zużycie pamięci, nawet osiągając limit i zawieszając się.

Benchmark

Oto wykonany kod. Zostało to zaczerpnięte z bloga Pascala Martina, a dokładniej z jego artykułu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
klasa Node {
publiczny $rodzicWęzeł;
publiczny $węzły potomne = szyk();
funkcjonować Node() {
$ to->nodeValue = str_repeat("0123456789", 128);
}
}
funkcjonować utwórz związek() {
$rodzic= nowa węzeł();
$dziecko = nowa węzeł();
$rodzic->dzieckoWęzły[] = $dziecko;
$dziecko->rodzicNode = $rodzic;
}
dla ($i = 0; $i 500000; $i++) {
utwórz związek();
// Ta część jest wykonywana podczas drugiego testu
if ($opcje[„gc_manual”]) {
gc_collect_cycles();
}
}

Wykonuję trzy testy osobno:

  1. Wykonuję ten test z podstawowymi opcjami. Garbage Collector jest włączony i PHP uruchamia go regularnie. Skrypt ma czas trwania Sekund 7.55 i wykorzystana pamięć sięgnęła 20.98 Mo.
  2. Wykonuję ten sam test, wyłączając Garbage Collector i wywołując go przy każdym zakręcie pętli. Skrypt trwa 3.79 sekundy a wykorzystana pamięć osiągnęła szczyt 244.77 KB.
  3. Przeprowadzam trzeci test, wyłączając Garbage Collector i nigdy nie zbierając ręcznie. Pamięć musi więc mocno się zapełniać. Skrypt trwa 4.46 sekundy i pamięć sięgnęła 1.98 Go.

Dzięki temu testowi możemy wyraźnie zobaczyć, jak ważne jest zarządzanie pamięcią. W naszym przykładzie, doprowadzonym do skrajności, wyraźnie widać wagę. Z jednej strony Garbage Collector wykonuje dużo pracy w zakresie zużycia pamięci, ale to spowalnia wykonywanie skryptu. Bardzo dobrze widać to porównując 1. test (z GC) i 3. test bez zarządzania.

Nasza praca nad wydajnością jest wykonywana w teście 2. Brak automatycznego zarządzania pamięcią, ręczne zarządzanie zoptymalizowane w tym skrypcie. Udaje nam się przyspieszyć szybkość przetwarzania prawie dwukrotnie (7.55 s / 3.79 s = 1.99). W ten sposób, ograniczyliśmy również naszą pamięć do 245 KB w stosunku do 21 MB do automatycznego zarządzania. To jest współczynnik prawie 88 ((20.98 * 1024) / 244.77 = 87.77).

Wnioski

Chociaż powyższy przykład przesuwa zarządzanie pamięcią do granic możliwości, ten test ma na celu pokazanie nam, ile to może być. ważne jest, aby studiować zarządzanie pamięcią w naszych skryptach. W niektórych przypadkach wypłaty mogą być imponujące. Czas przetwarzania, pamięć i wszystko, co się z tym wiąże, można zapisać.

Zrozum i używaj referencji

Jak powiedziałem ci trochę wcześniej, PHP implementuje przekazywanie zmiennych przez referencję i jak powiedziałem powyżej, ten rodzaj przekazywania jest mniej więcej równoważny z przekazywaniem przez wskaźnik.

  • Zrozumienie przekazywania przez referencję
  • Zrozumienie przekazywania wskaźnika
    Jest to ważna część nie tylko dla PHP, ponieważ to pojęcie istniało dużo wcześniej, ale także dla IT. Zasadniczo, kiedy deklarujesz funkcję, przejście jest wykonywane przez kopiowanie.

    // Przekaż publiczną funkcję kopiowania foo($arg) {…}

    To oznacza jakieś za i przeciw w zależności od twojego kodu. Obiekt jest kopiowany, co oznacza, że ​​przydzielona pamięć zwiększy się o wagę przekazanego obiektu. Może to być śmieszne, jeśli przekażesz wartość logiczną, ale może być o wiele ważniejsze, jeśli na przykład przekażesz tablicę.

1
2
3
4
5
6
7
8
9
10
11
publiczny funkcjonować bla() {
$a = 1;
słupek($a);
przegapić $a; //$a wynosi 1
}
publiczny funkcjonować bar($ argument) {
$arg = 2;
}

W tym przypadku widzimy, że wartość nie została zmodyfikowana, może to być interesujące, jeśli użyjemy zmiennej $a jako bazy i nigdy nie chcemy dotykać jej wartości. Można by tu mówić o nieistniejącym w PHP pojęciu zmiennej stałej (const).

1
2
3
4
5
6
7
8
9
10
11
12
publiczny funkcjonować bla() {
$a = 1;
$a = słupek($a);
przegapić $a; //$a wynosi 2
}
publiczny funkcjonować bar($ argument) {
$arg = 2;
powrót $argument;
}

Jestem pewien, że pisałeś już takie metody. W tym przypadku jest to ewidentny błąd. Oczywiście składnia jest całkowicie prawdziwa, ale dokonano alokacji dla kopii parametru i dokonano cofnięcia alokacji zmiennej. Drogie wywołania pamięci i niepotrzebny czas przetwarzania. Poniższy formularz byłby równoważny, ale znacznie szybszy.

1
2
3
4
5
6
7
8
9
10
11
publiczny funkcjonować bla() {
$a = 1;
słupek($a);
przegapić $a; //$a wynosi 2
}
publiczny funkcjonować bar(&$ argument) {
$arg = 2;
}

Tutaj przeprowadziliśmy dokładnie ten sam zabieg, ale jest on po prostu szybszy i mniej czasochłonny.

Ta optymalizacja jest bardzo prosta do wykonania i może nawet sprawić, że Twój kod będzie łatwiejszy do odczytania. Istnieje kilka składni, które należy znać, aby wykonać przekazanie przez referencję.

  1. Parametr przekazywany przez referencję (które właśnie widzieliśmy)
  2. Powrót funkcji przez referencję (nie wyszczególnione tutaj, ponieważ silnik PHP optymalizuje tę część i nie odczułem żadnego przekonującego zysku)
  3. Kopiowanie zmiennej przez referencję, niezbyt przydatne, ale groźne. Pozwalam ci przeczytać dokumentację PHP, która jest niezwykle szczegółowa. Można było łatwo dowiedzieć się rzeczy, które pominąłem 🙂

Inna optymalizacja

Dzięki powyższym trzem optymalizacjom nie powinieneś mieć problemu z przyspieszeniem przetwarzania. Istnieją jednak inne elementy. Wymieniam interesujące artykuły na temat różnych optymalizacji, które możesz zrobić.

  • PHP String Concat vs Array Implode

Wnioski

Trzy części, które właśnie ci przedstawiłem, stanowią dobrą alternatywę dla mojego gustu, aby zoptymalizować swoje skrypty i uniknąć ponownego uruchamiania rozwoju w szybszy język jako język skompilowany.

Zrobiłem te trzy przejścia na jednym ze swoich scenariuszy i między innymi mi się to udało zmniejszyć zużycie pamięci o 3 mniej więcej i osiągnąć przyrost prędkości o 2.

★ ★ ★ ★ ★