Zdarza się niestety czasami, że strona, na którą wchodzimy ładuje się bardzo długo chociaż jest to często spowodowane rodzajem połączenia (eh.. modem). Możliwe iż czas oczekiwania wydłuża skrypt php, dlatego w tekście postaram się wam przybliżyć zasady optymalizacji skryptów oraz pisania ich w możliwie przejżyszty sposób. Zaoszczędzi wam to w przyszłości kłopotów z poprawianiem skryptów i będziecie mogli zganiać wszystko na telekomunikację :).
Co znaczy optymalizacja?
Mój słownik mówi, co następuje: Optymalizacja (optymizacja) to wyznaczanie przy użyciu różnych metod najlepszego, najkorzystniejszego rozwiązania problemu ze względu na wybrane kryteria.
Optymalizacja skryptów będzie polegała na skróceniu czasu generowania strony, zmniejszeniu obciążenia serwera czyli po drodze zmniejszeniu liczby zapytań do bazy danych. Bardzo ważna jest jeszcze optymalizacja kodu, która jest pierwszym krokiem do uzyskania porządnych wyników.
Krok pierwszy - optymalizacja kodu.
Jeśli pracujesz w grupie styl pisania jest bardzo ważny, ponieważ wielu programistów używa różnej stylistyki. Może to być przycznyną powstawania kłopotów lub konfliktów. Duże znaczenie ma też program jaki używamy. Przykładowo pan Kowalski używa najprostszego notatnika spod windowsa, podczas gdy jesgo współpracownik preferuje PHP-Edit. Dodatkowo do grupy należy jeszcze pan Kwiatkowski, używający tylko i wyłącznie program Zend Studio. Gdy Kowalski otworzy jakiś plik napisany przez współpracowników ma dużą szansę, że zamiast kolejnych lini będzie widział kwadraciki. Jeśli współpracownik otworzy plik napisany przez Kwiatkowskiego to PHP-Edit sprubóje dostosować wcięcia do swoich.. czym kompletnie zamaże kod. Taki łańcuch przyczynowo skutkowy można rozpisywać i rozpisywać :).
Gdy pracujesz sam nie musisz się martwić o współpracowników, wystarczy, że dobrze się orientujesz w tym, co napisałeś, ale.. pamiętaj, aby zawsze komentować bardziej złożone części kodu, podać w komentarzu struktórę tablicy, tak abyś wiedział za jakiś czas jak wygląda tablica i dlaczego stosujesz pętlę for dwa razy a nie raz for i while lub foreach. Poniżej możecie zobaczyć funkcję mojego autorstwa, którą napisałem dzień wcześniej. Dzisiaj gdy się okazało że jest gdzieś błąd nie mogłem go znaleść (łoś ze mniec prawda? :)). Funkcja ma zmienić struktórę tablicy.
// przed zmina
id =>
[0] => 1
[1] => 2
tresc =>
[0] => tresc 1
[1] => tresc 2
// po zmianie
0 =>
[id] => 1
[tresc] => tresc 1
1 =>
[id] => 2
[tresc] => tresc 2
*/
function res_flap( $id )
{
for( $n = 0, $s = sizeof( $this -> cols[ $id ] ) -1; $n < $s; $n++)
{ // kolumny
$pole = $this -> cols[ $id ][ $n ];
for( $i = 0, $d = sizeof( $this -> cols[ $id][$pole] ) -1; $i < $d; $i++)
{ // wiersze
// zmien z $this -> res[$id][nazwa_pola][i] = wartosc_pola;
// na $this -> res[$id][nazwa_pola][i] = wartosc_pola;
$bla[ $i ][ $pole ] = $this -> res[ $id ][ $pole ][ $i ];
}
}
$this -> res[ $id ] = $bla;
// dalej do sortowania
}
Błąd schował się niezbyt głęboko, ale nie pamiętając co struktóry tablicy $this -> res nie mogłem dojść czemu? :). Wiecie, o co chodziło? Zamiast $bla[ $i ][ $this -> cols[ $id ][ $n ] ] było $bla[ $i ][ $this -> cols[ $id ][ $i ] ]. W końcu, kiedy przenalizowałem funkcję od początku do końca znalazłem błąd. Funkcja wymagała poprawek. Teraz działa już dobrze :).
Gdy wczoraj wieczorem postanowiłem poprawić kilka funkcji (bez sprawdzania) i komentowałem zmiany o wiele łatwiej odnajdowałem w nich błędy. Z resztą wyobraźcie sobie, że w samej funkcji nie ma komentarza.
Jak widzicie po moim przykładzie odpowiednie komentowanie kodu pomaga znaleść błąd. Przynajmniej dzisiaj nie siedziałem 15 minut nad dwudziestoma liniami kodu.
Staraj się znaleść rozwiązanie, jeżeli musisz pisać nazwę jakiegoś ineksu więcej niż trzy razy twórz zmienne tymczasowe. Wyobraźcie sobie, że w powyższej funkcji cały czas zamiast zmiennej $pole używałem $this -> cols[ $id ][ $n ] :).
Nie bój się wbudowanych funkcji - z internetu można pobrać podręcznik php w formacie .chm (plik pomocy), który znacznie ułatwia wyszukiwanie funkcji i działa szybciej niż manual w postaci wielu plików html. Własne funkcje, choćby najbardziej zoptymalizowane nigdy nie dorównają funkcjom wbudowanym, ponieważ są parsowane.
Dobrze zoptymalizowany kod ułatwia dalszą pracę i optymizację.
Krok drugi - zmniejszenie liczby zapytań do bazy danych.
Wiem, że to może wydawać się głupie, bo: po co odciążać bazę i komplikować sobie życie, skoro to ona miała trzymać dane, a tym samym ułatwiać mi pracę?
Powód jest prosty - często host zastrzega sobie, że rozmiar bazy danych nie może przekroczyć np. 2 megabajtów, chyba że się dokupi kolejne :). Dodatkowo wiele osób traktuje bazę danych jak śmietnik i przechowuje w niej zbędne rzeczy. Totalnym nieporozumieniem jest wykorzystywanie bazy danych do licznika, mniejszym do księgi gości. Oczywiście nie każdy ma dostęp do bazy, tym radzę ominąć ten krok i wrócić do niego jak znajdą porządnego hosta :).
Wyobraźcie sobie obciążenie serwera, który ma obsłużyć 30 zapytań gdy strona ma tysiąć rządań na godzinę. Dodam, że jest to standartowa ilość zapytań dla systemów portalowych. Jeżeli korzystasz ze skryptu forum - phpBB2 - możesz włączyć mechanizm cache, który zmniejszy ilość zapytań. To samo tyczy się skryptów korzystających ze Smarty. Klasa ta ma ogromne możliwości cache znacznie przyśpiesza generowanie strony.
Miałem kiedyś zadanie polegające na tworzeniu obrazka ze znakiem wodnym (pisałem o tym wcześniej). Nawet gdy liczba zapytań nie przekraczała dziesięciu to na obrazki trzeba było czekać i czekać. W pasku statusu pojawiał się komunikat, że pozostało x elementów. Zmartwiony tym srodze postanowiłem zbudować własny mechanizm cache (odkrywcze? :)).
Moja strategia była prosta, po dodoaniu obrazka lub edycji był on zapisywany na serwerze. Gdy nastąpiło rządanie był on po prostu wczytywany za pomocą funkcji imagecreatefromjpeg() i pokazywany dzięki imagejpeg(). W ten sposób straciłem kolejne 2 zapytania i przyśpieszyłem generowanie obrazka o 0,03 sekundy na moim przedpotopowym sprzęcie. Nie jest to wiele, ale przy większej ilości miniatur efekt jest odczuwany.
Kolejnym sposobem na zmniejszenie liczny zapytań jest stosowanie plików tymczasowych. Gdy do bazy zostanie dodany nowy rekord wartość w takim pliku jest zwiększana o 1. Jeżeli coś się usunie wartość pliku jest proporcjonalnie zmniejszana. Zamiast używać zapytania można po prostu wczytać plik.
<?php
$sql = "SELECT * FROM newsy";
$res = mysql_query( $sql );
$count = $res ? mysql_num_rows( $res ) : 0;
echo "Wszytkich nowości ". $count;
// plik temp/news.php wygląda tak: $count = 92; ?>
include "temp/news.php";
echo "Wszytkich nowości ". $count;
?>
Przyznacie, że sposób jest prosty, ale równocześnie zmiejsza ilość zapytań. Pamiętajcie - bazy danych są bardzo szybkie, ale bardzo obciążają serwer. Nie jest problemem napisać skrypt działający w oparciu o bazę danych, ale skrypt działający na plikach z porównywalnymi możliwościami. Wiele osób boi się plików, co IMO jest nieuzasadnione. Wystarczy odrobina wyobraźni!
Krok trzci - zmiejszamy odciążanie serwera
Obiciążenie serwera zostawiłem na koniec i właściwie niewiele mogę tutaj dopisać. Oczywiście pamiętajcie - mniej pracy wymaga wysłanie zwykłej strony html a więcej przetworzenie jakiegoś skryptu i dopiero wysłanie strony. Teraz z racji że to już końcówka przedstawię wam prostą funkcję, która pozwala sprawdzać czas generowania strony.
<?php
function czas()
{
$czas = microtime( );
$czas = explode( " ", $czas );
return $czas[1] + $czas[0];
}
$start = gen_www();
// tutaj twój kod
// i na samym końcu
$stop = gen_www();
$cos = substr( $stop - $start, 0, 7 ); // przycinamy do 7 znaków
echo "Czas generowania strony {$cos}s"; // no i pokaż wynik
// przykładowy wynik:
// Czas generowania strony 0.00133s
?>
Funkcja microtime( ) zwraca ilość milisekund, a następnie po spacji tzw. czas uniksowy - czyli liczbę sekund od początku ery uniksa (1 stycznia 1970, 0:00:00 GMT). Można powiedzieć że funkcja ta gdyby nie jej pierwszy człon mogłaby być aliasem funkcji time( ).
Prawdę mówiąc to sam czas generowania strony nie jest wielki :). Wiele zabiera szybkość przesyłania danych do użytkownika i żądań do serwera, dlatego często więcej zależy od połączenia użytkownika niż samego skryptu :/. W każdym razie nie martwcie się - im szybciej się uwinie serwer tym szybciej stronę otrzyma użytkownik :).
Teraz kolejna przydatna rzecz - ale działąjąca tylko, gdy używa się do obsługi baz danych klas. Przykładowa funkcja wykoująca zapytanie:
<?php
class sql
{
var $q_count = 0;
var $r_count = 0;
var $ostatnich = 0;
// tutaj konstruktor i inne mało interesujące sprawy :)
// funkcja odpowiedzialana za wykonanie zapytania - odpowiednik mysql_query();
function query( $query )
{
$this -> q_count++; // ilość zapytań +1
// zapytaj
if( !$this -> result = @mysql_query( $query ) )
{
return false;
}
// dodaj ilość wierszy w wyniku
$wierszy = $this -> num_rows( $this -> result );
$this -> r_count += $wierszy;
// zamiastt wywolywac drogi raz num_rows
$this -> ostatnich = $wierszy;
// zwróć wynik
return $this -> result;
}
// dalsze metody klasy
}
?>
W łatwy sposób można później wyciągnąć ilość wierszy i zapytań. Przykładowo:
<?php
$db = new sql( "user", "pass", "host" );
$db -> select( "baza" );
// kod wykorzystujący bazę danych.
echo "Wykonano ". $db -> q_count ." zapytań. W wynikach ". $db -> r_count ." wierszy";
?>
Można się tak bawić w nieskończoność :). Warto dodać jeszcze jedną zmienną w klasie - przykładowo $ostatnich. Zmienna ta przechowuje ilość wierszy z ostatniego zapytania. W ten sposób omijamy niepotrzebne, drugie (w sumie) wywołanie funkcji zwracającej ilość wierszy w wyniku.
To na razie byłoby wszystko na temat optymalizacji co mogę wam przekazać. Myślę że z czasem każdy z was dojdzie do lepszych technik optymizacji skryptów napisanych w php. Tymczasem przetestujcie swoje prace i spróbujcie je zoptymalizować. Zobaczcie czy coś to da :). |