.NET i takie tam

.NET CF – uwagi o pamięci/szybkości działania

with 4 comments

Microsoft ma swoje ‚best practices’ i ja mam swoje😉 Generalnie to co przedstawię poniżej to taki własny, nieuporządkowany, ‚core dump’ różnych informacji, wygrzebanych gdzieś po zakamarkach różnych dokumentacji i poradników. Wskazówki, głównie, dotyczą problemów z pamięcią i szybkością działania aplikacji pracujących pod kontrolą .NET Compact Frameworka i systemów Windows Mobile.

  • Im większy rozmiar pliku *.exe tym wolniej aplikacja będzie się ładować. Gdzieś tam wyczytałem, że szybkość ładowania w przybliżeniu wynosi 1Mb na 1s. Samemu nie mierzyłem, wierzę na słowo. Główną przyczyną spowolnienia jest kontrola uprawnień. Aby zmniejszyć rozmiar możemy nie podpisywać naszego assembly, klucz publiczny swoje zajmuje (oczywiście nie zawsze może to być możliwe). Po drugie warto zrezygnować z umieszczania zasobów wewnątrz naszej aplikacji (embedded resources) i ładować je w miarę potrzeb w trakcie działania aplikacji.
  • Właściwości (properties) klas są traktowane, przez kompilator, jako odwołania do metod. Czasem więc lepiej skorzystać z publicznego pola, szczególnie w obiektach intensywnie wykorzystywanych w wszelkiego rodzaju pętlach. Z podobnych względów dobrze jest, do minimum, ograniczyć zastosowania takich słów kluczowych jak abstract czy virtual. Pamiętajmy jednak, że data binding czy LINQ operują jedynie na właściwościach.
  • Koszty wywoływania metod:
    • wywołanie metody z instancji klasy to w przybliżeniu 2-3x koszt wywołania funkcji z .NET CF (ciekawe czemu taka różnica?)
    • wywołanie metody wirtualnej to ok. 1.4x wywołania metody z instancji klasy zarządzanej
    • wywołanie metody przy pomocy mechanizmu P/Invoke to ok. 5x kosztu wywołania metody z instancji klasy zarządzanej.
  • Pętla foreach wywołuje metody wirtualne na kolekcji – szybciej będzie jak skorzystamy z pętli for.
  • Po zakończeniu pracy z kolekcją zadbajmy o jawne usunięcie jej elementów – zmniejszymy tym samym pracę jaką będzie musiał wykonać garbage collector.
  • Minimalizujemy ilość tworzonych śmieci. Pracując z łańcuchami znaków pamiętajmy o klasie StringBuilder.
  • Starajmy się zminimalizować operacje boxing/unboxing.
  • Nigdy nie korzystamy z operacji GC.Collect() – osobiście jeszcze nigdy nie spotkałem sytuacji wymagającej jej zastosowania. Zwykle kiedy chcemy ją wywołać jest już za późno i niepotrzebnie spowalniamy działanie naszej aplikacji. Garbage Collector aby wykonać swoje zadanie musi zatrzymać wszystkie wątki. .NET Compact Framework automatycznie wywołuje GC.Collect(); zanim wyrzuci OutOfMemoryException.
  • Obsługa wyjątków nie stanowi większego obciążenia, chyba, że sami rzucamy wyjątki (throw). Nigdy nie korzystamy z wyjątków do kontroli pracy aplikacji. Chwytamy tylko te wyjątki którym możemy zaradzić, całą resztę obsługujemy w zdarzeniu UnhandledException.
  • Ograniczamy korzystanie z mechanizmu refleksji. O ile wykorzystanie typeof(…) nie stanowi większego problemu, to dostęp do pamięci przy urzyciu Type.InvokeMember() jest już od 10 do 100 razy wolniejsze od wywołania zwykłej metody.
  • Pamiętajmy o nadpisaniu metod ToString(), GetHashCode(), Equals() – domyślne implementacje wykorzystują refleksję.
  • Praca z xml’em wymaga użycia refleksji.
  • Korzystanie z web services oznacza korzystanie xml’a – dobrze jest przechować referencję w pamięci i nie tworzyć ją za każdym razem gdy korzystamy z usługi.
  • Korzystając z statycznych form dobrze jest je utworzyć w tle np. przy starcie aplikacji, a dane przekazywać tuż przed ich wyświetleniem.
  • Jeśli generujemy zawartość formy w trakcie działania aplikacji pamiętajmy o korzystaniu z metod SuspendLayout/ResumeLayout.
  • Pamiętajmy operatorze -= przy pracy z wyjątkami, nie wiem jak z aktualną wersją .NET Compact Framework ale w starszych wersjach pozostawienie przypisanego zdarzenia, prowadziło do wycieków pamięci w trakcie zamykania formy.
  • Motoda Dispose() jest po to aby z niej korzystać po zakończeniu pracy z obiektem. Bardzo częstym problemem jest nie wywoływanie jej dla obiektu SqlCeCommand. Koszt wywołania operacji z bazą danych SQL Compact Edition w głównej mierze zależy od ilości danych a nie rodzaju zapytania. Im więcej danych tym więcej natywnych zasobów zajmuje obiekt SqlCeCommand ich nie zwolnienie prowadzi do wycieków pamięci. Pamiętajmy, klauzula using(…) jest naszym przyjacielem.
  • Korzystamy z obiektów SqlCeDataReader bądź w bardziej wymagających wypadkach SqlCeResultSet. Zapominamy o takich obiektach jak DataSet czy DataTable. Jedynym wyjątkiem jaki w tym momencie przychodzi mi na myśl jest zastosowanie Microsoft Synchronization Services for ADO.NET (przynajmniej, na tyle ich jeszcze nie poznałem aby stwierdzić czy nie da się inaczej).
  • SqlCeConnection – dyskusji o tym czy powinno się korzystać z jednego połączenia czy wielu, podczas pracy z aplikacją mobilną, toczyło się wiele. Generalnie jeśli naszym problemem jest szybkość aplikacji korzystamy tylko z jednego, cały czas, otwartego połączenia. Natomiast borykając się z brakiem pamięci, dobrze jest otwierać, zamykać i niszczyć połączenie w miarę potrzeb, gdyż alokuje ono pewną ilość zasobów natywnych.
  • Każda aplikacja pracująca w systemie Windows Mobile 6 ma do dyspozycji 32Mb przestrzeni adresowej i musi się w niej nasza aplikacja łącznie z bibliotekami .NET CF.
  • Każda dodatkowa biblioteka dołączona do naszej aplikacji, niezależnie od jej rozmiaru, zajmuje minimum 64Kb, więc jeśli korzystamy z dużej ilości małych plików dll (zwykle np. 10-20kb) może okazać się, że marnujemy dużą ilość przestrzeni adresowej.

Wiele z tych porad wydaje się całkiem oczywistych, niektóre jednak wydadzą się zapewne dziwne, wręcz przeczące zdrowemu rozsądkowi. Pamiętajmy, jednak , że piszę tutaj o nie tylko o okrojonej wersji .NET Framework’a ale również zmodyfikowanej do potrzeb i możliwości urządzeń mobilnych.

Zdarza się, że w niektórych prezentacjach/tutorialach (zwykle tych wprowadzających) autorzy piszą, że aplikacje mobilne pisze się tak samo jak aplikacje desktopowe. Jest to całkowicie, nieprawda, fakt .NET CF pozwala nam korzystać, w większości przypadków, z tych samych funkcji co desktopowy odpowiednik, jednak założenia i sposób implementacji, dobrej, aplikacji powinny być inne.

Written by sakowicz

Grudzień 2, 2008 @ 10:30 am

Odpowiedzi: 4

Subscribe to comments with RSS.

  1. Jeżeli chodzi o foreach to nie jest takie oczywiste, w CF 3.5 dla 40 elementów for był co prawda tylko o 1ms ale jednak dłuższy niż foreach

    Qurylack

    Grudzień 15, 2008 at 10:21 am

  2. To jest właśnie problem testowania prędkości, przeprowadziłeś ten test kilka razy i wyciągnąłeś średnią? Nigdy nie wiesz co akurat system operacyjny robi w tle i jak to wpływa na szybkość działania Twojej aplikacji.
    Poza tym proponował bym testować tego typu operacje, nie na kilkudziesięciu elementach a raczej na kilku-set czy kilku-tysiącach, dopiero wtedy różnice w szybkości działania zaczynają być widoczne.

    sakowicz

    Grudzień 16, 2008 at 1:45 pm

  3. Test został przeprowadzony 15 krotnie, 1 ms to najmniejsza różnica jaką osiągnąłem.
    Myślę, że na kilku tysiącach test wyjdzie na korzyść pętli for, chodzi mi o to że dla małych list for wcale nie musi być lepszy, a może być nawet minimalnie gorszy

    Qurylack

    Grudzień 17, 2008 at 10:04 am

  4. Dlatego właśnie sami powinniśmy testować nasze aplikacje, a nie ślepo wierzyć ‚dobrym praktykom’ bo każdy przypadek jest inny.

    sakowicz

    Grudzień 17, 2008 at 10:48 am


Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d bloggers like this: