Prosta optymalizacja powtarzającej się pętli

0

Witam wszystkich.

Mam taką metodę, która jest wykonywana bardzo często, np przez timera lub przez event MouseMove. i w tej metodzie mam dajmy na to taki kod:

 
void metoda(double a, double b)
{
    double x = a * b;
    (.....)
}

Wszystko fajnie tylko że zmienna x używana w tej metodzie jest deklarowana bardzo często za każdym wywołaniem, więc zastanawiam się czy nie jest wydajniej zrobić taki kod:

 
double x;
void metoda(double a, double b)
{
    x = a * b;
    (.....)
}

Trochę to nieładne ale czy bardziej wydajne? Czy może kompilator jest mądrzejszy niż myślę i sobie z tym radzi? Pół biedy jeśli to double, ale jeżeli mam tam jakieś złożone typy to czasem się martwię. Tym bardziej że dokładnie takie problemy mam bardzo często.

0

Dla double to nie ma znaczenia, ale np. dla CultureInfo różnica byłaby ogromna. Wszystko zależy od tego jakiej klasy są to obiekty, możesz w ten sposób próbować optymalizacji.
Zapoznaj się tez z klasą Stopwatch i zmierz nią czas wykonywania poszczególnych metod, może to Ci da jakieś wnioski.

0

W JVMie jest eksperymentalny Escape Analysis, który optymalizuje alokacje. W wielu przypadkach redukuje alokacje obiektów prawie do zera, jeśli tylko część obiektu jest wykorzystywana i nie wydostaje się on poza funkcję. Może w CLR jest to już domyślne?

Pewnie deus wie, ale się zagubił i nie widzi tematu.

0

Właśnie myślałem że deus to ma w małym palcu :P

Klasom typu stopwatch jakoś nie wierzę bo nigdy mi się nie udawało wiarygodnie mierzyć tak krótkich czasów.
Ale mówisz że dla double to nie ma znaczenia czy że dla double ewentualne opóźnienie jest pomijalne? Bo to jest różnica.

0

Ja obstawiam, że lokalne dla procedury prymitywy (char, double, int itp) są alokowane na stosie, a alokowanie na stosie to tylko zmniejszenie wskaźnika stosu, jedno odejmowanie. Wybieraj, jedno odejmowanie, albo większa zajętość pamięci :) Dodatkowo jeśli korzystasz z lokalnych pól i są one alokowane na stosie to z dużym prawdopodobieństwem (większym niż w przypadku nielokalnych zmiennych) są już w keszu procesora, a co za tym idzie dostęp jest szybszy.

1

Mam taką metodę, która jest wykonywana bardzo często, np przez timera lub przez event MouseMove

Przede wszystkim - obsługa MouseMove albo timera jest milionkrotnie bardziej zasobożerna niż ta drobna różnica.

Wyobraź sobie to tak:Twoja metoda jest wywoływana przez zdarzenie (które nie są wybitnie szybkie), wywoływane przez okno po tym jak dostało WM_MESSAGE - i przeleciało wewnętrzny słownik zarejestrowanych zdarzeń po kluczu otrzymanej wiadomości - dostało WM_MESSAGE przez wywołanie - poprzez ślamazaaaarne PInvoke - funkcji WinAPI GetMessage() która zwróciła kilka wartości przez wskaźniki zapakowane następnie do zarządzanych obiektów .net (i która wywołuje prawdopodobnie inne metody do kilkunastu warstw zagnieżdżenia, możliwe że przechodząc przez SYSENTER aż do kernela - ale to już tylko moje domysły - ale na pewno przeglądając kilka list w poszukiwaniu zarejestrowanych wcześniej zdarzeń), po czym te wartości zostały zapakowane w (utworzony ofc dynamicznie przez new) zarządzany obiekt typu Message i przepchnięte przez kilka warstw abstrakcji do okna.

A teraz pomyśl co zmieni twoje kilka w tą czy tamtą stronę taktów procesora.

0
PATRZEŹ napisał(a)

Klasom typu stopwatch jakoś nie wierzę bo nigdy mi się nie udawało wiarygodnie mierzyć tak krótkich czasów.

A słyszałeś o czymś takim jak pętla? Wsadź sobie do fora ten swój kod, wykonaj tysiąc albo milion razy i będziesz wiedział która wersja szybsza.

0

Somekind:
Ale takie testowanie jest zbyt stronnicze. Może mu np wyjść że mają taką samą prędkość bo w takim prostym przypadku kompilator zoptymalizuje kod do takiej samej postaci, ale w bardziej złożonych przypadkach dopiero może być problem.

Ja jestem za tym, aby zmienne miały jak najmniejszy zasięg, więc zagnieżdżam je najbardziej jak się da. Na pewno kompilatorowi łatwiej jest optymalizować taki kod, bo ma mniej zależności do przeanalizowania.

0

Dlatego uważam, że dla double to raczej nie ma znaczenia, a testować w zależności od potrzeby należy każdy, a zwłaszcza bardziej złożony przypadek. Poza tym - jeśli okaże się, że wywołanie metody trwa 1 czy 2 ms, a poszukujemy czegoś, co zajmuje 1 sek, to na pewno nie jest to, ale zawsze warto się zorientować.

Ogólnie masz rację co do zasięgów zmiennych. Ale jeśli metoda danej klasy jest wołana wielokrotnie i używa zasobożernego obiektu, to nie ma sensu tworzyć go za każdym razem, lepiej niech będzie polem klasy. Kompilator takiej optymalizacji nie przeprowadzi.

0

Nawet jeśli Escape Analysis nie zadziała, bo np konstruktor będzie komunikował się z innymi procesami, to i tak można zawęzić zasięg poprzez zastosowanie klasy wewnętrznej na przykład. Ta klasa wewnętrzna przetrzymywałaby owy zasobożerny dodatkowy obiekt. Dzięki temu ten zasobożerny obiekt byłby od razu zebrany przez odśmiecacz, gdy klasa wewnętrzna wyleciałaby z zasięgu użycia.

Referencje należy nullować od razu po ostatnim użyciu, dzięki zawężaniu zasięgu mamy to czytelnie, elegancko i bez pisania kodu nullującego. Jeżeli nie będziemy nullować od razu to mamy ryzyko wycieków pamięci, a to wiadomo co dalej za sobą pociąga.

Pamiętaj, że w przypadku kodu zarządzanego kompilator może dowolnie głęboko inlineować kod i dewirtualizować metody, dzięki czemu Escape Analysis ma dużo większe pole do popisu niż w przypadku statycznie kompilowanych języków takich jak C/ C++.

0

Rozwiązanie z klasą wewnętrzną faktycznie może coś polepszyć, pod warunkiem, że obiekt klasy zewnętrznej żyje długo. W przeciwnym przypadku wiele to nie zmieni. :)

Ja nie wiem czy tu jest w ogóle miejsce na Escape Analysis - to potrafi wyciągnąć z pętli, czy też metody wywoływanej w pętli obiekt lokalny na zewnątrz? Zresztą, w CLR chyba nie ma tego mechanizmu.

0

Escape Analysis służy generalnie/ pierwotnie do alokowania obiektów na stosie zamiast na stercie. Jednak dokonywane analizy pozwalają np na częściową inicjalizację obiektów, a w połączeniu z inlineowaniem i dewirtualizacją można np wyczaić które pola będą takie same w kolejnych konstrukcjach obiektów (to ostatnie to pewnie na razie niezaimplementowane).

Escape Analysis pomaga znacznie w przypadku Scali. Tam wszystko jest wirtualizowane do granic możliwości i np mogą być sytuacje, gdy w pętli tworzy się przy każdym przebiegu obiekt, który np tylko zwiększa licznik pętli. Escape Analysis z JVMa w takim przypadku redukuje w ogóle alokację tego obiektu i wkleja kod bezpośrednio do pętli, dzięki czemu wydajność jest taka sama jak ze zwykłą inkrementacją zmiennej.

Rozwiązanie z klasą wewnętrzną faktycznie może coś polepszyć, pod warunkiem, że obiekt klasy zewnętrznej żyje długo.

No jeśli ten "obiekt tymczasowy" żyje tak długo jak sama klasa, to na pewno nie nazwałbym go tymczasowym.

0
Wibowit napisał(a)

Escape Analysis służy generalnie/ pierwotnie do alokowania obiektów na stosie zamiast na stercie.

W CLR to obiekty wartościowe idą na stos, a referencyjne na stertę i nie słyszałem o jakimś mechanizmie ulepszania tego.

No jeśli ten "obiekt tymczasowy" żyje tak długo jak sama klasa, to na pewno nie nazwałbym go tymczasowym.

Mi chodzi bardziej o taki przykład:

class A
{
    B b = new B();
    void Metoda()
    {
        for(int i = 0; i < 10000000; i++)
        {  
            b.ZróbCoś();
        }
    }
}

Więc b nie jest "tymczasowy" wewnątrz klasy A, ale jeśli obiekt klasy A jest tworzony gdzieś raz tylko po to, żeby wywołać raz Metode, to b też będzie tymczasowy.

0

W CLR to obiekty wartościowe idą na stos, a referencyjne na stertę i nie słyszałem o jakimś mechanizmie ulepszania tego.

To jest co innego - explicite podajesz gdzie ma być alokowany obiekt. Escape Analysis automatyzuje to i o wiele więcej.

W tym przykładzie jeżeli A jest długotrwały, a metodę Metoda wykonujesz sporadycznie i tylko w pewnym jednym momencie, to generalnie niewynullowanie pola b albo zbyt wczesne zainicjalizowanie nazwałbym wyciekiem pamięci - po co ma istnieć obiekt, o którym nie wiemy czy się przyda, albo o którym wiemy, że się już nie przyda?

Ogólnie w sumie uzasadnione takie kombinacje, zakładając że CLR jest na tyle prosty, że nie umie sobie poradzić z optymalizacjami tak jak JVM (może kiedyś dojdą mu takie ficzery), ale i tak jest to gmatwanie kodu. Przy klasie wewnętrznej zachowanie jest standardowe, tutaj musimy pamiętać o jakiejś ekstra semantyce czy schematach użycia.

Jeżeli wywołujesz metodę Metoda przez całe życie obiektu klasy A i to regularnie, to pole b nie jest tymczasowe, więc takie sytuacje pomijamy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1