Jeden czy wiele plików resource, a wydajność

Wprowadzenie

W wpisie 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 pisałem w innym wpisie (Używanie napisów w aplikacji) stosuje wiele plików resource do organizacji napisów wyświetlanych użytkownikowi. Dlatego chciałem sprawdzić, czy 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ż jednego, ale narzut nie będzie miał dużego wpływał na działanie aplikacji. Jedynie pierwsze pobranie wartości z pliku wiąże się z załadowaniem danych z dysku, przez co będzie mniej wydajne. Ile wolniejsze? Zaraz sprawdzimy to za pomocą BenchmarkDotNet.

Przykład do testów

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

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

Przygotowany plik ma cztery wersje: domyślną oraz 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ść ciężko 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 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. Natomiast drugi 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. Natomiast utworzenie instancji oraz pierwsze pobranie już około 315 razy. Na wnioski przyjdzie jeszcze czas na koniec wpisu.

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

Jak widać kolejne odczytywania wartości jest już podobnie szybkie jak pierwsze testy, więc dodatkowy narzut jest 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ło by 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 to 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 widać, że odczyt pierwszej wartości z pliku resource jest zauważalnie wolniejszy niż każdy kolejny. Prawdopodobnie to nikogo nie zdziwiło.

A jaka jest odpowiedź na pytanie z tytułu? Jeden, czy wiele plików resource?

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

Ja w swoich dalej zostaje przy wielu plikach resource i sposobu organizacji napisów opisanego w poprzedni wpisie. W aplikacjach webowych, które najczęściej tworze łączny narzut kilku milisekund (na ogół mam do 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 u mnie w projektach używanie jednego (lub bardzo małej ilości plików) nie sprawdzało się. Bardzo często przy mergach różnych branch 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 branch.

Korzystając w pull requestów takie konflikty wydłużają cały proces pracy nad zmianami. Wciągnięcie jednego branch do mastera powodowało w innych pull requestach konflikty, przez co nie można było ich automatycznie zmergować i trzeba było to ręcznie mergować. Co 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 *