Jeden czy wiele plików resource a wydajność

Wprowadzenie

Pod wpisem o lokalizowaniu enumów pojawił się komentarz, w którym Janko zwrócił mi uwagę, że rozbijanie napisów na małe pliki resource nie jest najlepszym pomysłem pod względem wydajności. Jak zaznaczyłem w innym wpisie (Używanie napisów w aplikacji), stosuję wiele plików resource do organizacji napisów wyświetlanych użytkownikowi. Dlatego chciałem sprawdzić, czy to, co pisze Janko, to prawda.

Przeglądając Internet, można trafić na pytania na stackoverflow: https://stackoverflow.com/questions/8792180/1-resource-file-vs-many czy https://stackoverflow.com/questions/19910926/multiple-resource-files-versus-single-resource-file. Wynika z nich, że stosowanie wielu plików resource będzie mniej wydajne niż stosowanie jednego, ale narzut nie będzie miał dużego wpływu na działanie aplikacji. Jedynie pierwsze pobranie wartości z pliku wiąże się z załadowaniem danych z dysku, przez co ów proces będzie mniej wydajny. A o ile wolniejszy? Zaraz sprawdzimy to za pomocą BenchmarkDotNet.

Przykład do testów

Przygotowałem bardzo prostą aplikację konsolową, w której znajduje się jeden plik z zasobami: Resources. Plik zawiera 10 napisów. Podczas testów będziemy pobierać jeden z napisów.

Gdy przeprowadzałem test, wyszło mi, że nie ma znaczenia, czy pobieramy jeden i ten sam napis, czy za każdym razem inny. Czas pobrania napisu jest taki sam (oczywiście poza pierwszym pobraniem, jak zobaczymy później). Dlatego właśnie w testach zawsze pobieram jeden i ten sam napis.

Przygotowany plik ma cztery wersje: domyślną oraz wersje dla kultur: pl-PL, de-De, en-GB:

resources performance solution explorer

Ile trwa pobranie wartości z pliku resource?

W pierwszej kolejności sprawdzimy pobranie wartości z pliku resource. Wynik ten posłuży jako punkt odniesienia do kolejnych testów.

Etap rozgrzewania z BenchmarkDotNet spowoduje, że pomiar zmierzy tylko odczyt wartości z cache, bez ładowania ich z dysku. Test wygląda tak:

Wyniki wykonanego testu:

Test odczytania jednego napisu z resource trwał około 97 nanosekund.

Niestety dość trudno napisać test dla klasy wygenerowanej na podstawie pliku resource, który sprawdzi, ile czasu trwa pierwszy odczyt wartości. Dlatego po analizie kodu klasy postanowiłem, że w kolejnych testach będę wykorzystywał bezpośrednio klasę ResourceManager. Klasa wygenerowana przez Visual Studio tylko w ładny sposób opakowuje wywołania metod z klasy ResourceManager.

Dla pewności przygotowałem drugi test, który zmierzy czas wykonywania metody GetString, aby upewnić się, że czas będzie podobny do czasu z pierwszego testu.

Korzystanie bezpośrednio z klasy ResourceManager daje podobny wynik, dlatego w kolejnych testach będą ją wykorzystywał.

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 20 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Ile trwa utworzenie ResourceManagera?

Dwa kolejne testy sprawdzą to, co jest najistotniejsze w dzisiejszym wpisie. Pierwszy sprawdzi, ile trwa samo utworzenie instancji klasy ResourceManager. Drugi natomiast dodatkowo wykona pobranie jednego zapisu. Testy oraz wyniki prezentują się tak:

Jak widać, utworzenie instancji klasy jest około 26 razy wolniejsze niż późniejsze pobranie danych z cache. Utworzenie instancji oraz pierwsze pobranie jest już natomiast około 315 razy wolniejsze. Na wnioski przyjdzie jeszcze czas pod koniec wpisu.

Poniżej jeszcze jeden test, w którym po utworzeniu instancji klasy ResourceManager dodatkowo wykonywanych jest 1000 odczytów napisu – aby sprawdzić, czy nic dodatkowo nie wpływa jeszcze na czas pobrania wartości.

Jak widać, kolejne odczytania wartości są już podobnie szybkie jak pierwsze testy, więc dodatkowy narzut pojawia się tylko na początku korzystania z klasy ResourceManager.

A co z innymi językami?

Wcześniejsze testy pobierały dane z domyślnego pliku resource. Przygotowałem również trzy inne pliki dla poszczególnych kultur. Należałoby sprawdzić, jak wygląda pobranie tego samego napisu dla różnych kultur. Jakie tutaj będą czasy?

Test jest bardzo podobny do testu trzeciego, czyli: tworzymy nową instancję klasy ResourceManager i następnie dla każdej dodatkowej kultury odczytujemy ten sam napis:

Wynik jest zbliżony do trzykrotności wyniku trzeciego testu, więc widać, że narzut mamy również dla każdej poszczególnej kultury, którą wspieramy w aplikacji.

Przykład do uruchomienia

Na githubie (https://github.com/danielplawgo/OneVsManyResourceFiles) znajduje się przykład, który wykorzystałem podczas pracy nad tym wpisem. Zachęcam do jego pobrania i własnych testów.

Jest to prosta aplikacja konsolowa, która uruchamia przygotowane testy BenchmarkDotNet. Po pobraniu projektu należy go skompilować w trybie release i uruchomić aplikację z katalogu bin/release.

Jeden czy wiele plików resource – podsumowanie

Analizując wyniki testów, można zauważyć, że odczyt pierwszej wartości z pliku resource jest zauważalnie wolniejszy niż każdy kolejny. Prawdopodobnie nikogo to nie zdziwiło.

A jaka jest odpowiedź na pytanie pojawiające się w tytule? Jeden czy wiele plików resource?

Odpowiedzieć musisz sobie sam, drogi Czytelniku. Wszystko zależy od tego, jakie aplikacje tworzysz.

Ja w swoich nadal zostaję przy wielu plikach resource i sposobie organizacji napisów omówionym w poprzedni wpisie. W aplikacjach webowych, które najczęściej tworzę, łączny narzut kilku milisekund (na ogół mam od kilkudziesięciu do kilkuset plików resource) nie jest czymś problematycznym. Szczególnie gdy jest on jednorazowy i rozłożony w czasie (na ogół 0,1 ms raz dla jednego widoku, gdy mamy plik resource per plik widoku).

Z drugiej strony w moich projektach używanie jednego pliku (lub ich bardzo małej liczby) nie sprawdzało się. Bardzo często przy mergach różnych branchy pojawiało się dużo konfliktów właśnie na takich wspólnych plikach. Konflikty nie były trudne do rozwiązania, na ogół wystarczyło dodać zmiany z obu branchy.

Przy korzystaniu z pull requestów takie konflikty wydłużają cały proces pracy nad zmianami. Wciągnięcie jednego brancha do mastera powodowało w innych pull requestach konflikty, przez co nie można było ich automatycznie zmergować i trzeba było zrobić to ręcznie. A to było dużo kosztowniejsze niż dodatkowe kilka milisekund raz na kilka dni działania aplikacji webowej.

A jaka jest Twoja odpowiedź? Jeden czy wiele plików?

1 thought on “Jeden czy wiele plików resource a wydajność

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *