Mit o kolekcjach…

Od pojawienia się oficjalnie siódmej (3.12.2015) wersji języka #PHP minęło już troszkę czasu, a w miarę jego upływu zapewne wielu z was słyszało mit (lub też nie mit) o tym, że iterowanie się po obiektach jest szybsze a niżeli iterowanie po tablicach. Mit jest potocznie znany, ale wydaje mi się, że jest przekazywany pocztą pantoflową… bo nie potrafiłem znaleźć żadnych sensownych wpisów w Internecie  na ten temat (może źle szukam ! – if yes then fix).

Performance matter

Mając na uwadze cykl wpisów pod serią #performanceMatter, postanowiłem rozpocząć od takiego lekkiego wpisu. Skoro już w naszych przekonaniach występuje powyższy mit (o wyższości obiektów względem tablic – w kontekście ich iterowania), to nie pozostaje mi nic innego jak sprawdzić na żywym organizmie czy to prawda – a jeśli tak, to na jaki wzrost wydajności możemy liczyć.

Wymyślmy sobie prostą procedurę testową:
Utwórzmy kolekcję zawierającą dane obiektowo oraz tablicowo, o następujących wielkościach:
100 elementów (100 elementów odpowiada standardowej paginacji danych per strona);
500 elementów (500 elementów odpowiada standardowo za maksymalną ilość elementów per strona);
1000 elementów oraz 20000 (10000 oraz 20000 odpowiada przygotowanej porcji danych do wszelakiego rodzaju raportów);
– a na sam koniec 100000 oraz 300000 elementów.
Następnie iterujmy się po wybranych kolekcjach odczytując, zmieniając oraz kopiując poszczególne elementy.
Każdy test zostanie wykonamy 5-krotnie, następnie wyciągniemy średnią z sumy wszystkich 5-iteracji każdego testu.

Obiekt z jakiego będziemy korzystać, wygląda następująco:

Analogicznie, tablica będzie posiadała klucze:
id
firstName
lastName
fullName
comment
accessLevel
Dane testowe będą przygotowane za każdym razem w ten sam sposób – prezentowany poniżej:

Maszyna testowa (wirtualna) posiada 4 CPU natomiast przydzielona pamięć RAM to 4048MB.
Wirtualizacja wykonana za pomocą Hyper-V.
System operacyjny na maszynie wirtualnej: Ubuntu 19.04
PHP w wersji: 7.3.11-1+0~20191026.48+debian9~1.gbpf71ca0 (cli) (built: Oct 26 2019 14:18:28) ( NTS )
Pomiarów natomiast będziemy dokonywać za pomocą blackfire.io.

Każdy przypadek testowy będzie wyglądał bardzo podobnie do przykładu podanego poniżej, głównie zmieniać się będzie ilość elementów, sposób przechowywania informacji obiekt/tablica oraz ciało pętli.

Do dzieła

Test #1

Pierwszy test wygląda następująco:

A oto i wyniki dla poszczególnych ilości elementów w iterowanej kolekcji:

Wyniki dla testu #1 – (im mniej tym lepiej)

Test #2

W teście drugim będziemy natomiast modyfikować wartości aktualnie przeglądanego elementu kolekcji, dla przykładu będziemy mnożyć aktualne id elementu przez ilość wszystkich elementów:

A oto i wyniki dla poszczególnych ilości elementów w iterowanej kolekcji:

Wyniki dla testu #2 – (im mniej tym lepiej)

Test #3

W trzecim teście kopiujemy aktualny element do nowej tablicy oraz zmieniamy wartość id w starym elemencie (kolejny raz mnożymy razy ilość wszystkich elementów).

A oto i wyniki dla poszczególnych ilości elementów w iterowanej kolekcji:

Wyniki dla testu #3 – (im mniej tym lepiej)

A co z stdClass ??

A co wprzypadku gdybyśmy zamiast własnego zdefiniowanego obiektu chcieli wykorzystać stdClass ? – jest to przecież dynamiczny obiekt, zachowujący się trochę jak tablica. Skoro ona sobie tak dobrze poradziła to może on także ?

Wykonałem wszystkie 3 testy uwzględniając stdClass dla 20000, 100000 oraz 300000 elementów.
A oto i wyniki wraz z porównaniem poprzedników.

Wyniki dla testu #1 – (im mniej tym lepiej)
Wyniki dla testu #2 – (im mniej tym lepiej)
Wyniki dla testu #3 – (im mniej tym lepiej)

Podsumowanie

Jak widać mit rozpowszechniany pocztą pantoflową o wyższości kolekcji obiektowych nad tablicowymi (w kontekście szybkości operacji / iteracji) jest tylko mitem (przynajmniej w wersji PHP 7.3)

Tylko pierwszy test wykazał szybszy dostęp do danych w trybie odczytu jeśli iterujemy się po kolekcji z samymi obiektami względem konkurentów (muszę tutaj przyznać, że test ten powtórzyłem 20-krotnie – wyniki były takie same).
Niemniej jednak różnice czasowe przy niektórych wielkościach kolekcji są na poziomie akceptowalnym. Inaczej sprawa wygląda przy dużych kolekcjach.

Tutaj warto zwrócić uwagę, że czasy dużych kolekcji sięgają ms (milisekund) – dla jednego przejścia ! Natomiast przygotowując porcje danych do np. raportów, może się zdarzyć, że ilość iteracji będzie rosła !
`
Drugim ważnym punktem na który chciałbym zwrócić uwagę, to fakt iż często największy narzut czasowy jest przydzielony do hydracji danych przez biblioteki zewnętrzne, dlatego czasem warto się zastanowić czy przetwarzając wielkie zbiory danych musimy działać na zhydrowanych obiektach… pamiętajmy #performanceMatter 😉

Kod źródłowy testów, dla samodzielnego wykonania…
Szczegółowa tabelka z wynikami dla każdego z testów (wliczając użycie pamięci)