Posts tagged PHP

XAMPP, Windows i najnowszy PHP

Wiele osób korzysta z pakietu XAMPP jako serwer developerski na platformie Windows. Z mojego doświadczenia mogę powiedzieć, że sprawdza się on bardzo dobrze. Przez wszystkie wersje pakietu nie miałem problemów aż do wersji 1.7.4. Niestety mimo wielu prób, szukania informacji na forum, stosowania najprzeróżniejszych rozwiązań nie udało mi się doprowadzić tego pakietu do stanu używalności. Głównym problemem jest to, że najnowsza wersja została skompilowana pod platformę VC9 (która jest przystosowana do serwera IIS), a większość pakietów jest dostępna w wersji VC6. Poza tym w wersji VC9 na serwerze Apache nie działa to ze sobą zbyt dobrze.

Do tej pory używałem wersji 1.7.3, która działa bez najmniejszych problemów, ale ma już swoje lata a potrzebny był mi nowszy PHP (w tej wersji jest to 5.3.1)

Jak więc posiadać dobrze działający pakiet XAMPP z najnowszą wersją PHP?

  1. Ściągnij i zainstaluj wersję 1.7.3, która działa bez najmniejszych problemów http://sourceforge.net/projects/xampp/files/XAMPP%20Windows/1.7.3/ chyba, że już posiadasz ten pakiet.
  2. Ściągnij paczkę PHP dla Windows (obecnie najnowsza wersja 5.3.6 jest dostępna tylko w wersji VC9 a nam potrzebna VC6, więc ściągnij stąd http://windows.php.net/downloads/releases/archives/ pakiet o nazwie php-5.3.5-Win32-VC6-x86.zip
  3. Wypakuj pakiet php-5.3.5-Win32-VC6-x86.zip gdzieś na dysk
  4. Przekopiuj zawartość wypakowanego katalogu do C:\xampp\php
  5. Podmień stary plik php.ini na nowy php.ini-development lub php.ini-production
  6. Ciesz się XAMPP’em z najnowszym PHP

PHP 5.3.3 i konstruktor

Podczas pracy nad frameworkiem napotkałem na bardzo dziwny błąd (jak mi się wtedy wydawało). Miałem klasę “Index” i metodę “index”. Podczas tworzenia instancji klasy ciągle mi się uruchamiała automatycznie ta metoda. Doszedłem chyba po 2 godzinach do tego, że to nie błąd tylko feature. Już tak dużo czasu minęło, że zapomniałem, że PHP traktuje metody o takiej samej nazwie jak klasa jako jej konstruktor. Trochę mnie to załamało, bo moje MVC jest oparte o takie założenie, a twórcy PHP zostawili taką możliwość jako wsteczną kompatybilność.

Na szczęście doczytałem, że od wersji PHP 5.3.3 zmieniło się zachowanie szukania konstruktorów i teraz tego typu metoda traktowana jest jako zwykła metoda a nie jako konstruktor. Uff!

Wydajność dostępu do pól klasy

Przedstawiona teoria dotyczy wielu języków interpretowanych… m.in PHP i Python.

Jeżeli posiadasz złożone klasy zawierające wiele pól (czy to statycznych i dynamiczny) i w swoich metodach wykorzystujesz je dosyć często, np w ten sposób:

	public static function fetchAll($query = null, $args = array())
	{
		$sql = 'SELECT ' . static::$_columns . ' FROM ' . static::$_table . (isset(static::$_joinLeft[static::$_table]) ? static::$_joinLeft[static::$_table] : null);

		if ($query) {
			$sql .= ' WHERE ' . $query;
		}

		$collection = array();

		if (empty(self::$_selectQuery[static::$_table]) || self::$_selectQuery[static::$_table] != $sql) {
			self::$_selectStatement[static::$_table] = Database::getInstance(static::$_database)->prepare($sql);
			self::$_selectQuery[static::$_table] = $sql;
		}

		foreach($args as $k => $v) {
			self::$_selectStatement[static::$_table]->bindValue($k+1, $v);
		}

		self::$_selectStatement[static::$_table]->execute();

		// --

 		return $collection;
	}

to w takim przypadku system będzie każdorazowo musiał odpytać do pamięci o to pole. Niestety ale tak to działa. Można znacznie przyspieszyć wykonywanie tego kodu poprzez stworzenie lokalnej wartości, tak aby nie było konieczności każdorazowego odwoływania się do klasy. Zrobiłem szybki test w którym stworzyłem 4 klasy:

class A {

	public static $_a = 4;

	public function a() {

		$a = self::$_a;
		$b = self::$_a;
		$c = self::$_a;
		$d = self::$_a;
		$e = self::$_a;
		$f = self::$_a;

	}
}

class B {

	public static $_a = 4;

	public function a() {

		$local = self::$_a;
		$a = $local;
		$b = $local;
		$c = $local;
		$d = $local;
		$e = $local;
		$f = $local;

	}
}

class C {

	public $_a = 4;

	public function a() {

		$a = $this->_a;
		$b = $this->_a;
		$c = $this->_a;
		$d = $this->_a;
		$e = $this->_a;
		$f = $this->_a;

	}
}

class D {

	public $_a = 4;

	public function a() {

		$local = $this->_a;
		$a = $local;
		$b = $local;
		$c = $v;
		$d = $v;
		$e = $local;
		$f = $local;

	}
}

Jak widać nie robią one nic niesamowitego, ale robią w zasadzie to samo… tylko, że w różny sposób:

Test polegał na utworzeniu instancji i wywołaniu na każdej instancji 1 mln razy metody a()

Wyniki są następujące:

dla Windows

A: static 1.1116s
B: static with local 0.6764s
C: dynamic 0.9799s
D: dynamic with local 0.6258s

Jak widać, poprzez utworzenie zwykłej zmiennej, która ma dostęp lokalny uzyskujemy prawie 2x przyspieszenie wykonywania kodu.

Pliki konfiguracyjne i aplikacje w PHP

Ostatnio po raz kolejny mierzę się z problemem przechowywania informacji konfiguracyjnych dla aplikacji napisanych w języku PHP. Jak to zrobić? Czego użyć? Co będzie najprostsze i najbardziej wygodne i przede wszystkim dla mnie…. co będzie najszybsze? Oczywiście chciałbym znaleźć złoty środek, który łączy wszystkie te cechy w jednym. Czy tak się da? Zobaczymy… ale najpierw mały wstęp:

Nie wszystko złoto co się świeci

PHP to taki dziwny język. Przez jednych kochany przez innych nienawidzony. Jednak jest w nim coś takiego co sprawia, że mnie cały czas zadziwia i ciągle uczę się o nim czegoś nowego. Ciekawe jest to, że pewne rzeczy, które wydawałoby się, że powinny działać lepiej/szybciej od innych zachowują się zupełnie odwrotnie.

Osoby, które mają jakieś pojęcie o językach takich jak np C i starają się pisać kod w sposób wydajny i pamięciooszczędny mają wyrobione pewne reguły postępowania, które pomagają ten cel przybliżyć takie jak:

  • wskaźniki do obiektów
  • zmienne właściwego typu
  • zarządzanie pamięcią
  • dyrektywa define
  • itd

Co ciekawe… z racji tego, że język PHP jest składniowo bardzo podobny do języka C++ a ponadto występują w nim podobne konstrukcje to naturalnym odruchem wydaje się chęć ich używania. Niestety tutaj możemy wpaść w pułapkę. Przede wszystkim trzeba sobie za każdym razem przypomnieć, że aplikacja w języku PHP i jego specyfika jest zupełnie inna niż ta w języku C. Weźmy na tapetę “define”. W jeżyku C taka konstrukcja #define PI  3.14 pozwala “nazwać” pewną stałą wartość np liczbę 3.14 nazwać PI i zamiast pisać w kodzie 3.14 używamy PI. Kompilator zamieni wszystkie wystąpienia PI na liczbę 3.14.  Dzięki temu uzyskamy bardzo wydajny kod, jednocześnie mając wszystko ładnie poukładane i pod kontrolą. Oczywiście można tworzyć o wiele bardziej skomplikowane rzeczy ale nie o to teraz chodzi. Wydawałoby się, że to samo można zastosować do PHP. Przecież tutaj tez mamy konstrukcję define(“PI”, 3.14); Jednakże tutaj pojawia się problem. Trzeba wziąć pod uwagę, że w aplikacji napisanej w jezyku C interesuje już nas samo uruchomienie. Natomiast w PHP na każde uruchomienie skryptu składa się komplikacja i wykonanie. W związku z tym, nie daje to żadnego przyspieszenia – a wręcz zwalnia to program. PHP więcej czasu potrzebuje na uruchomienie funkcji define niż stworzenie zwykłej zmiennej. Takie przykłady można mnożyć jednak po tym przydługim wstępie chciałem przejść do meritum:

Kod PHP wolniej działa w PHP

Nagłówek może nieco wydawać się enigmatyczny lub pozbawiony sensu ale istocie oddaje on całą prawdę. Okazuje się, że niektóre konstrukcje samego języka PHP mogą działać wolniej niż funkcje dostarczone do języka. Nawiązując do tematu tego posta stanąłem przed problemem zdecydowania się na system, za pomocą którego mógłbym przechowywać dane konfiguracyjne aplikacji w języku PHP (ściślej mówiąc projektowanego frameworka). Co mnie interesowało i wziąłem pod uwagę:

  1. Serializowana tablica
  2. JSON
  3. plik INI
  4. plik PHP z tablicą

Żeby zbyt wiele nie wróżyć postanowiłem przeprowadzić testy wydajnościowe. Format danych który chciałbym załadować to węzeł x1, który zawiera 2 pary wartości y1 = 1 oraz y2 = 2 oraz węzeł x2, który zawiera 2 pary y3= 1 oraz y4 = 2

Dla każdej z metod zapisałem dane w odpowiednim formacie:

serializowana tablica

a:2:{s:2:”x1″;a:2:{s:2:”y1″;s:1:”1″;s:2:”y2″;s:1:”2″;}s:2:”x2″;a:2:{s:2:”y3″;s:1:”1″;s:2:”y4″;s:1:”2″;}}

JSON
{“x1”:{“y1″:”1″,”y2″:”2″},”x2”:{“y3″:”1″,”y4″:”2”}}

plik INI
[x1]
y1 = 1
y2 = 2
[x2]
y3 = 1
y4 = 2

plik PHP z tablicą
$conf_2 = array(
‘x1’ => array( ‘y1’ => 1, ‘y2’ => 2 ), ‘x2’ => array( ‘y3’ => 1, ‘y4’ => 2 ));

Procedura testowa

Test polegał na 10000 krotnym wywołaniu danej funkcji i odczytaniu danych. Dla każdej z metod wykonałem odpowiednie procedury:

serializowana tablica

$conf_3 = unserialize(file_get_contents(‘./array.ser’));

JSON
$conf_5 = json_decode(file_get_contents(‘./array.json’), TRUE);

plik INI
$conf_1 = parse_ini_file(‘./array.ini’, true);

plik PHP z tablicą
require(‘./array.php’);

Wyniki testu

Windows 7 (PHP 5.3.1)

  1. Plik INI (0.9728s)
  2. Require (1.0595s)
  3. Unserialize (1.0884s)
  4. JSON (1.1447s)

Linux (PHP 5.3.6)

  1. JSON (0.1267s)
  2. Plik INI (0.1744s)
  3. Unserialize (0.1892s)
  4. Require (0.2466s)

Podsumowanie

W tym momencie ciężko wyłonić zwycięzce. Dziwi mnie tylko tak duża różnica w wydajności działania funkcji json_decode między platformą Windows i Linux. Co prawda nie jest to ta sama wersja PHP ale nie powinno to mieć wpływu na działanie tej funkcji. Test ten wskazuje prędkość działania konkretnych rozwiązań, ale nie mówi on nic o czytelności i łatwości korzystania a tutaj nie trudno wskazać zwycięzce – moim zdaniem plik INI jest w tej kwestii bezkonkurencyjny. Po pierwsze większość edytorów koloruje składnie plików INI. Poza tym (co może być uważane także za minus) można w nim stworzyć tylko rzeczy, które służą strice do konfiguracji – a więc nie ma obawy, że ktoś przecholuje i zacznie wrzucać tam jakieś funkcje, dziwne zależności, zmienne globalne itd. Poza tym parser plików INI w PHP potrafi także interpretować stałe (np E_NOTICE –  tak jakby widział je w kodzie) oraz przechowywać tablice. Jedyny minus to taki, że nie potrafi on skonwertować danych na rzeczywiste typu… np “true” to nie będzie w PHP “true” tylko “1” .. każda inna nieprawda  to będzie pusty ciąg znaków.

Ponadto widać tu także inną rzecz. Wykonanie funkcji “parse_ini_file”, która uruchamia funkcję mającą zaczynać plik z dysku, sparsować i pozmieniać jego dane na format rozpoznawalny przez PHP działa szybciej niż załadowanie już gotowego formatu danych PHP z pliku PHP za pomocą instrukcji języka. Dziwne? I tak nie. parse_ini_file jest funkcją napisaną w języku C przez co wykonuje się ona zdecydowanie szybciej niż instrukcja PHP. Myślę, że istnieje jeszcze wiele takich króczków i na pewno będę je dalej badał.

Chciałbym jeszcze wspomnieć, że oczywiście zdaję sobie sprawę z istnienia akceleratorów, optcode’u, gotowych klas parsujących (np w Zend Framework) języka YAML, memcache’a itd itd, ale chciałem w czystym środowisku ukazać specyfikę samego języka PHP aby poznać skąd się co wzięło i z czego wynika. Jeśli nie jesteśmy zmuszeni aby używać systemu poprawiającego działanie innego systemu to dobrze. W programowaniu im mniej skomplikowany algorytm tym lepiej.

Wydajne aplikacje w PHP

Jeżeli nasza aplikacja pracuje pod dużym obciążeniem odpowiednie zoptymalizowanie jej jest kluczowe.

W tym artykule chciałbym zaprezentować różne często popełniane błędy w podejściu do pisania aplikacji w języku PHP a także sztuczki i propozycje, które poprawiają wydajność. Postaram się pokazać jak różne niewydajne elementy zastąpić elementami wydajnymi. Niektóre zabiegi są naprawdę bardzo proste a poprawiają wydajność aplikacji nawet kilkukrotnie (oczywiście w skali makro). Aby zobaczyć różnicę w wydajności będę konkretne operacje powtarzał w pętli po 100 lub 1000 razy, żeby uzbierał się odpowiedni czas na testowanie. Pozwoli to odwzorować przeciętne obciążenie strony.

Maszyna testowa:

Procesor: P4 3,2 GHz (s478 Northwood)
Pamięć: 2 GB DDR Dual (Kingston)
Dysk: 2x 60GB WD (Raid 0, SATA)
Płyta: Abit IC-7g

Windows Vista, XAMPP (PHP 5.3)

Elementy obciążające PHP

Przygotowuje sobie w międzyczasie implementację ActiveRecord do środowiska Prime. Jednym z elementów założenia jest zwrócenie listy obiektów (aktywnych rekordów), z których każdy posiada zestaw wartości (kolumny wiersza) oraz metody. W zasadzie jest instancją takiego samego obiektu jak element potomny.

zwykłe wykonanie kodu, który za pomocą PDO, wykonuje zapytanie do bazy i zwraca po prostu tablicę z wynikami przy 1000 wywołaniach:

Mniej więcej wygląda to tak:

	public function select($where = null, $args = array()) {

		$sql = 'SELECT ' . $this->_columns . ' FROM ' . $this->_table;

		if ($where) {
			$sql .= ' WHERE ' . $where;
		}

		$data = array();

		$stmt = $this->_dbh->prepare($sql);

		foreach($args as $k => $v) {
			$stmt->bindValue($k+1, $v);
		}

		$stmt->execute();

		while ($row = $stmt->fetch(PDO::FETCH_OBJ)) {
			$data[] = $row;
		}

		$stmt->closeCursor();

		return $data;
	}

I teraz test:

$Test = new Test();
for($i=1;$i<1000;$i++)
	$a = $Test->select();

Jak widać. Klasa wykonuje odpala zapytanie 1000 razy i zwraca obiekt z wynikami. Obiekt PDO typu FETCH_OBJ

To daje średni czas: 0,688 sekundy

Problem zaczyna się gdy zwrócona lista nie ma być zwykłą listą wyników lecz listą obiektów tej klasy. Przedstawia się to mniej więcej tak

public function findAll($conditions = '', $arguments = array(), $order = '', $limit = '')
 	{
		$add = null;

		if (strlen($order) > 0)
 			$add .= ' order by '.$order;

		if (strlen($limit) > 0)
 			$add .= ' limit '.$limit;

		$rows = $this->_select($conditions, $arguments, $add);

		$classname = get_class($this);
 		$retval = array();

		foreach($rows as $row) {
			$obj = new $classname();
 			$obj->loadFromArray($row);
 			$retval[] = $obj;
		}

 		return $retval;
 	}

I test:

$Test = new Test();
for($i=1;$i<1000;$i++)
	$a = $Test->findAll();

W takim oto zestawieniu czas wynosi średnio: 5,1265 sekundy

Ups! Chyba trochę za dużo. Zaczęły się więc poszukiwania “wąskiego gardła”. Jak widać, w pętli metody findAll tworzy się za każdym razem obiekt tej samej klasy (żeby rezultat był active recordem) Dotarłem więc do konstruktora, którzy wygląda tak:

	public function init($classname, $conditions = '', $order = '')
 	{
 		if (isset($this))
 			$obj = &$this;
 		else
 			$obj = new $classname();

 		if ($obj->table == '')
 			$obj->table = strtolower($classname);

 		if ($obj->data === false)
			$obj->clear();

		require_once 'Database.php';
		$this->_dbh = Database::getInstance();
 	}

Mamy tu jedną bardzo istotną rzecz: “require_once”. Każdy programista PHP wie do czego ta instrukcja służy ale jednak jak widać nie zawsze wiadomo jak właściwie ją zastosować. “_once” zapewnia nam, że ponowne wywołanie tej instrukcji już się nie wykona jeśli wykonało się wcześniej. Jeśli napisałbym “require” to za każdym razem require tego pliku by się wykonywał. Jakie to ma znaczenie dla aplikacji?

W sieci można znaleźć wiele testów mających na celu wykazanie wyższości jednego nad drugim, jednakże wiele z tego zależy od użytych systemów dodatkowych takich jak APC, systemu operacyjnego, sprzętu, buforowania dysku i pamięci podręcznej, itd – takie testy nie są zbyt pomocne jeśli nie można do końca przewidzieć środowiska którym aplikacja będzie pracować. Teoretycznie, “require” czy też “include” powinny być szybsze od swoich odpowiedników “_once”, ale trzeba wziąć pod uwagę ich zastosowanie.

Jeżeli taki test przeprowadzony jest wewnątrz aplikacji (tak jak u mnie), to “require” przegrywa na całej linii: Dla porównania:

1000x wykonany require_once: 3,423 sekundy
1000x wykonany require 6,356 sekundy

Jednak trzeba na problem spojrzeć z innej strony. W takim przypadku warto stosować zawsze funkcje “_once” gdyż:

  1. Działa ona szybciej gdyż nie wykonuje ponownie załączonego kodu
  2. Zapewnia, że aplikacja nie wyłoży się w momencie załadowania pliku jeszcze raz (bo się po prostu drugi raz nie załaduje – będzie już rezydowała w pamięci dla danego czasu życia aplikacji)

Gdy jednak spojrzy się na to pod względem testu całej aplikacji, tzn “1000x żądanie odpyta się skrypt” a nie “1000x skrypt odpyta plik” to wtedy warto zastosować “require” gdyż jest szybsze. Dokładniej przedstawić takie scenariusze:

  1. 1000 użytkowników -> request -> skrypt (załączenie pliku “A.php”)
  2. 1 użytkownik -> request -> skrypt (1000x załączenie pliku A)
  3. 1 użytkownik -> request -> skrypt (załadowanie 1000 różnych plików)

Jak należy postąpić w przypadku w/w scenariuszy?

1) Jeżeli pojawi się wiele requestów skryptu, który załącza sobie podelementy samego siebie ale tylko RAZ (np jakieś klasy do obsługi bazy, do obsługi wyjątków itd, i robi to tylko raz w ciągu działania aplikacji – użyj “require

2) Jeżeli jeden użytkownik uruchomi skrypt, który gdzieś ma 1000x załączenie tego samego pliku, użyj “require_once

3) Jeżeli jeden użytkownik uruchomi skrypt, który gdzieś ma załączenie 1000 różnych plików użyj “require” (jeśli robi to tylko raz, lub “require_once” jeśli robi to wiele razy)

Wszystko to sprowadza się do bardzo prostej konkluzji, która mówi, że używanie funkcji “require” jest jak widać dosyć czasochłonne, więc należałoby to robić jak najrzadziej.

Omawianą klasę do obsługi ActiveRecord wystarczy więc przerobić tak, aby nie robiła “require” za każdym razem gdyż jest to niepotrzebne. Jeżeli w swojej aplikacji przewidujesz, że ta klasa będzie używana w 99% przypadków to załącz ją do “rdzenia”

Zatem, gdy usunę tylko ten require z konstruktora, co prezentuje się tak:

	public function init($classname, $conditions = '', $order = '')
 	{
 		if (isset($this))
 			$obj = &$this;
 		else
 			$obj = new $classname();

 		if ($obj->table == '')
 			$obj->table = strtolower($classname);

 		if ($obj->data === false)
			$obj->clear();

		$this->_dbh = Database::getInstance();
 	}

Wynik testu zmienia się na: 0,913 sekundy

Przed zmianą było to: 5,1265 sekundy

A więc widać, że znaczący miało to wpływ.

Poprawianie wydajności

Udało nam się poprawić czas. Podstawowe zwrócenie wyniku przez PDO przypomnę, że było to: 0,688 sekundy

Zwrócenie obiektu danej klasy zdecydowanie opóźniło czas, ale dzięki różnym zabiegom udało się zejść z 5,1265 sekundy do 0,913 sekundy

Ale czy da się zrobić coś więcej? Tak.

Zacytuję jeszcze linijkę:

foreach($rows as $row) {
			$obj = new $classname();
 			$obj->loadFromArray($row);
 			$retval[] = $obj;
		}

Chcemy zwrócić tablice activerecordów, które są de facto obiektamy tej samej klasy. Po co jednak za każdym razem tworzyć nowy obiekt? Czy nie lepiej byłoby skopiować go i podmienić tylko wartości? Tutaj z pomocą przychodzi nam polecenie “clone“. Clone tworzy nam dokładną kopię danego obiektu jednak w postaci zupełnie osobnej, niezależnej instancji. Tworzy się dokładna kopia takiego obiektu i możemy na nim pracować zupełnie niezależnie – ale dzięki temu nie musimy tworzyć całej klasy od nowa, uruchamiać konstruktorów itd itd. Jak to wpływa na wydajność? Gdy zastąpimy tworzenie obiektu klonowaniem:

foreach($rows as $row) {
			$obj = clone $this;
 			$obj->loadFromArray($row);
 			$retval[] = $obj;
		}

Czas wykonania skryptu spada do: 0,804 sekundy

Nieźle? Praktycznie dotarliśmy do wyniku klasy, która robi tylko zwykłe zapytanie. Tak naprawdę w tym momencie nie musimy się już przejmować optymalizacją funkcji w klasie gdyż obciążenie będzie już wynikało tylko z tego, że sama klasa jest bardziej skomplikowana jako struktura. Na to już nic poradzimy. Widać jednak, że dzięki kilku prostym sztuczkom udało się bardzo mocno zoptymalizować działanie takiej aplikacji.

Go to Top