Przyspieszanie skryptów

javascript js code 1486361 1920 Przyspieszanie skryptów
4/5 - (1 vote)

Autor: Paweł Rajewski

Jednymi z najbardziej czasochłonnych operacji przeprowadzanych przez skrypty są działania na elementach strony. Już samo odczytanie właściwości obiektu (np. wysokości elementu DIV) trwa ok. stukrotnie dłużej niż odczytanie tej samej wartości ze zmiennej lub tablicy przechowywanej w pamięci. Co można z tym zrobić?

Mimo, że pojedyncze instrukcje wydają się działać błyskawicznie (kto zauważy różnicę między pół i jedną milionową sekundy?), to sytuacja dramatycznie pogarsza się w przypadku pętli. Mikrosekundy sumują się i… w efekcie skrypt może działać zbyt wolno. Szczególnie, gdy ma odpowiadać na czynności podejmowane przez użytkownika np. na ruchy myszki. Już 0,1-0,2 sek. opóźnienia pomiędzy „najechaniem” na obiekt a reakcją skryptu jest wyczuwalne. Czasami więc trzeba powalczyć nawet o mikrosekundy.

Jeżeli skrypt działa zbyt wolno (a algorytm jest optymalny), powinniśmy przyjrzeć się odwołaniom do obiektów. Im będzie ich mniej i im będą krótsze, tym szybciej zadziała funkcja. Oto kilka prostych reguł. (Wszystkie czasy zmierzone na komputerze z Fc=300MHz, Win95PL OSR2 i MSIE5.5PL. Strona zawierała 30 elementów DIV z różnymi identyfikatorami, w tym jeden z ID=”test”).

Nawiasy okrągłe

Odwołania do elementów strony poprzez kolekcje (np. all) mogą zawierać nawiasy kwadratowe lub kropki (typowa składnia tablicowa). Rozwiązanie to funkcjonuje, ale jest wolniejsze niż zalecane przez Microsoft z użyciem nawiasów okrągłych (zastosowanie nawiasów kwadratowych lub kropek wywołuje metodę item(), a zatem all[’test’] jest równoznaczne all.item(’test’)). Zmieniając nawias możemy skrócić czas odwołania o kilkanaście do kilkudziesięciu procent. Wykonanie pierwszej z poniższych instrukcji trwało na stronie próbnej średnio 517 ns, drugiej – 357 ns.

oTest=window.document.all['test']; 
oTest=window.document.all('test'); 

Brak obiektu window

Choć pisanie pełnych odwołań jest dobrym zwyczajem, czasem warto zrezygnować z niego na rzecz szybkości. Praktycznie każde odwołanie można pozbawić obiektu window, który przyjmowany jest jako domyślny. W większości przypadków (nie zawsze!) spowoduje to skrócenie czasu odwołania o kilka procent. Wykonanie pierwszej z poniższych instrukcji trwało średnio 357 ns, drugiej – 324 ns.

oTest=window.document.all('test'); 
oTest=document.all('test'); 

Skrócone odwołanie

Do obiektu posiadającego ID można odwołać się w sposób skrócony podając sam identyfikator. Internet Explorer traktuje identyfikatory tak samo jak nazwy zmiennych globalnych. Gdy przeglądarka natrafia na ciąg znaków sprawdza najpierw czy nie jest to zmienna lub identyfikator. Jeśli nie, przechodzi do domyślnego obiektu window gdzie szuka dalej. Podanie samego identyfikatora jest więc najszybszą drogą do obiektu. Metodę tę można stosować do wszystkich obiektów za wyjątkiem ramek i elementów formularzy.

Pierwsza z poniższych instrukcji trwała średnio 357 ns, druga – 198 ns.

oTest=window.document.all('test'); 
oTest=test; 

Ponieważ czas szukania obiektu rośnie wraz z ilością identyfikatorów obecnych na stronie, chcąc maksymalnie przyspieszyć skrypt należy nadawać ID tylko tym elementom, którym jest to niezbędne. Należy też zadbać, by na stronie nie pojawiły się zmienne globalne o takich samych nazwach jak identyfikatory obiektów. Jeśli tak się stanie, może dojść do konfliktu kończącego się (w najlepszym razie) nadpisaniem „skróconego odwołania” przez wartość zmiennej. Przy tym błędzie warto się na chwilę zatrzymać.

<DIV ID="test"></DIV> 
<SCRIPT TYPE="text/Jscript" LANGUAGE="JScript"> 
//var test=5; 
alert(test); 
</SCRIPT> 

Uruchom kod. Dowiesz się, że test to obiekt. Teraz usuń komentarz z linii poprzedzającej alert() i przeładuj stronę. Dowiesz się, że test jest równe 5 – skrót do obiektu został nadpisany i nie można go już używać. Teraz usuń jeszcze instrukcję var (pozostawiając samo test=5. Po uruchomieniu strony nastąpi błąd – test został zidentyfikowany jako obiekt, a temu nie można oczywiście przypisać wartości 5. Gdyby na stronie nie było identyfikatora ID=”test” zapis test=5 zadziałałby prawidłowo tworząc nową zmienną globalną.

Indeksy cyfrowe

Najszybszą metodą dostępu do obiektu jest posłużenie się indeksem cyfrowym. Zamiana identyfikatora na numer może nawet dwukrotnie skrócić czas odwołania (nie występuje tu proces szukania). Pierwsza z poniższych instrukcji trwała średnio 357 ns, druga – 127 ns.

oTest=window.document.all('test'); 
oTest=window.document.all(20); 

Zmniejszanie liczby bezpośrednich odwołań

Im mniej bezpośrednich odwołań do obiektów, tym szybciej zadziała skrypt. Już przy dwukrotnym dostępie do obiektu opłaca się wykonać skrót i korzystać z niego w drugim i kolejnych odwołaniach. Poniższe instrukcje odczytują szerokość i wysokość obiektu test. Pierwsza grupa instrukcji wykonywała to średnio w 880 ns:

iTestW=test.offsetWidth; 
iTestH=test.offsetHeight; 

Druga w 630 ns:

oTest=test; 
iTestW=oTest.offsetWidth; 
iTestH=oTest.offsetHeight; 

Ograniczenie zakresu poszukiwań

Szukając obiektu korzystamy najczęściej z kolekcji all obiektu document. W takiej sytuacji przeszukiwany jest cały dokument i wszystkie zawarte w nim elementy (zarówno w sekcji HEAD jak i BODY). A przecież niemal każdy element strony posiada własną kolekcję all zawierającą obiekty zawarte w tym elemencie. Po co zatem szukać na całej stronie, skoro można poszukać tylko we fragmencie? I rzeczywiście, to rozwiązanie jest szybsze.

Na testowej stronie zawarłem 20 elementów DIV, a w jednym z nich, o ID=”ala” umieściłem kolejnych 10 elementów w tym jeden z ID=”test”. Zakładając, że posiadam już skrót do elementu ala zawarty w zmiennej oAla, poszukałem obiektu test raz tradycyjnie, w całym dokumencie, a raz jedynie w obiekcie ala. Pierwsza z poniższych instrukcji trwała średnio 390 ns, druga – 220 ns.

oTest=window.document.all('test'); 
oTest=oAla.all('test'); 

Zastosowanie tej metody ma jednak sens tylko wtedy, gdy posiadamy już skrót do interesującego nas obiektu-fragmentu strony. Jeżeli wielokrotnie odwołujemy się do jakiejś grupy elementów, być może warto taki skrót wykonać, aby potem przyspieszyć odwołania i poszukiwanie obiektów.

Indeksy pętli

Wszystkie wymienione reguły dotyczą także indeksów pętli. Oto dwie wersje pętli wykonywanej tyle razy, ile wynosi szerokość części roboczej okna w px (w teście było to 1007). Pierwsza pętla, odczytująca wartość bezpośrednio z obiektu BODY, wykonywała się w ok. 170 ms:

for(i=0;i<window.document.body.clientWidth;i++){ 
}; 

Druga, korzystająca z tymczasowej zmiennej – w 4 ms (ponieważ pętla jest pusta, jest to czas zużywany na samo powtarzanie pętli, a więc doliczany do czasu wykonywania użytecznych instrukcji).

iBCW=window.document.body.clientWidth; 
for(i=0;i<iBCW;i++){ 
}; 

Warunki

Ta sama sytuacja wystąpi w przypadku warunków. Tu także im mniej bezpośrednich odwołań do obiektów, tym lepiej. Pierwsze z poniższych porównań dokonywane było w 170 ns:

if(i<window.document.body.clientWidth){ 
}; 

Drugie w 6 ns (zakładając, że zmienna iBCW zawiera już wartość window.document.body.clientWidth). Warto zauważyć, że ten warunek jest częścią podanej w poprzednim przykładzie pętli for i to jego przyspieszenie spowodowało skrócenie czasu wykonywania całej pętli.

if(i<iBCW){ 
}; 

W przypadku instrukcji warunkowych z kilkoma warunkami OR można skorzystać z ciekawej cechy interpretera JScriptu, jaką jest nie dokonywanie kolejnych porównań po znalezieniu pierwszego pozytywnego wyniku. Jeżeli więc mamy instrukcję:

Kod:
if(i<10||i>20){
};

…to dla i mniejszego niż 10 wykonane zostanie tylko jedno porównanie – interpreter „wie”, że znalezienie jednego wyniku pozytywnego wystarczy i nie ma sensu sprawdzanie następnych. Aby przyspieszyć porównania należy więc korzystać z konstrukcji OR i jako pierwsze wpisywać te warunki, które mają największe prawdopodobieństwo wyniku true. Np. jeśli i przybiera losowe wartości od 0 do 100, porównanie (i>20||i<10) będzie statystycznie szybsze niż (i<10||i>20), ponieważ i częściej będzie większe od 20, niż mniejsze od 10. Analogicznie, statystycznie szybsze będzie sprawdzenie czy liczba jest poza podanym zakresem (i<10||i>20) niż czy jest wewnątrz tego zakresu (i>=10&&i<=20). W pierwszym przypadku istnieje szansa, że zostanie dokonane tylko jedno porównanie, w drugim zawsze będą dokonywane obydwa.

Wszystkie podane tu reguły są słuszne, choć zyski na konkretnej stronie mogą bardzo różnić się od wyżej przytoczonych. Generalnie, im bardziej skomplikowana jest strona, i im więcej jest na niej obiektów z identyfikatorami, tym większe będą efekty optymalizacji. W praktycznych warunkach można liczyć na przyspieszenie skryptu o kilkanaście do kilkudziesięciu procent jedynie optymalizując odwołania do obiektów.

Paweł Rajewski

Skomentujesz?

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Administratorem Twoich danych osobowych będzie Rafał Płatek, prowadzący działalność gospodarczą pod firmą CREAM.SOFTWARE RAFAŁ PŁATEK, wpisaną do rejestru ewidencji gospodarczej CEiDG pod numerem NIP 681-112-89-55. Szczegóły związane z przetwarzaniem danych osobowych znajdziesz w polityce prywatności.