Tryb prywatny
Zanim przejdziemy do narzędzi. Chciałbym podkreślić najważniejszą rzecz. Najważniejsza jest odpowiednia izolacja takiego benchmarku i w ogóle testów wydajnościowych - najlepiej jest to robić w tzw. trybie prywatnym (incognito).
Nie musimy pamiętać o wszystkich elementach które mogą wpływać na nasze testy tj. o pluginach, cache, historii, ustawieniach profilu, ciasteczkach etc.
Narzędzia
Przejdźmy do narzędzi - najpopularniejszym jest ten przeklęty jsperf. Przeklęty ponieważ jest bardzo łatwy w (nad)użyciu. Prostota stworzenia testu i łatwość współdzielenia wyników przysłania bardzo często zdrowy rozsądek.
O nadużywaniu jsperf opowiemy sobie dokładnie za chwilę.
Inną kategorią narzędzi są strony porównawcze - Browserscope jest najpopularniejszą i najobszerniejszą bazą wiedzy nt. przeglądarek, również związaną z wydajnością.
Ostatnia kategoria to ciężka artyleria, którą krótko zaprezentowałem wcześniej - czyli tak naprawdę niskopoziomowe profilery dla V8/Node, które umożliwiają dosłownie na spojrzenie pod maskę silnika JavaScript i wykrycia potencjalnych błędów.
Microbenchmarks
Powiedzieliśmy sobie, że bardzo łatwo nadużyć jsperf. Prowadzi to do zjawiska zwanego Microbenchmarkingiem, czyli skupienie na bardzo małych fragmentach kodu i wyciśnięciu z nich siódmych potów.
Problem polega na tym, że środowisko przeglądarkowe nie jest dobrym miejscem do skupiania się na tego typu optymalizacjach, ponieważ mamy zbyt mały wpływ na nie.
W przykładzie widzimy prostą funkcję z 4 parametrami i będziemy testować narzut dla określonego sposobu jej wywołania (6 przypadków testowych).
You do it wrong!
Przez to, że nie mamy praktycznie żadnego wpływu na JIT zostaliśmy oszukani i co gorsza wyciągamy błędne wnioski pt. "W Chrome 32 znacznie przyspieszyli klasyczny sposób wywoływania funkcji".
Dlaczego?
function benchmark() {
function fn(a, b, c, d) {
return a + b + c + d;
}
var start = new Date;
for (var n = 0; n < 1e6; n++) {
fn('a', 'b', 'c', 'd');
}
return (new Date - start);
}
Dlaczego?
function benchmark() {
var start = new Date;
for (var n = 0; n < 1e6; n++) {
// Ponieważ znamy fn, rozwijamy ją w miejscu wywołania:
a + b + c + d;
}
return (new Date - start);
}
Dlaczego?
function benchmark() {
var start = new Date;
for (var n = 0; n < 1e6; n++) {
// Wynik nie jest nigdzie używany więc kompilator go usunie...
}
return (new Date - start);
}
Dlaczego?
function benchmark() {
var start = new Date;
// ... razem z pętlą for, która była teraz pusta ;).
return (new Date - start);
}
Ważniejsze jednak od tego jak to naprawić, jest zupełnie co innego. Powinniśmy skupiać się raczej na profilowaniu całości i optymalizacji całego rozwiązania, niż wyszukiwać najoptymalniejsze drobiazgi implementacyjne (za dużo przeglądarek, zbyt mały wpływ na środowisko, od tego jest JIT).
Don't take performance advices from strangers!
Na koniec dwie bardzo ważne rady - nie przyjmujmy rad dot. wydajności od obcych na ślepo. Nie szukajmy w internecie, na blogach, na forach odpowiedzi na Nasze pytania - to My znamy system i to My powinniśmy go zbadać.
Don't guess it, test it!
Nic dodać nic ująć - Najpierw zbadać, potem optymalizować - najpierw zmierzyć potem uciąć. Nie powinniśmy strzelać na oślep technikami optymalizacyjnymi (zasada Pareto 80/20 ma wszędzie zastosowanie) mimo, że z dużym prawdopodobieństwem trafimy to zmarnujemy czas i zasoby.