java, ile kosztuje wywołanie metody


82

Jestem początkującym i zawsze czytałem, że powtarzanie kodu jest złe. Jednak wydaje się, że aby tego nie robić, zwykle musiałbyś mieć dodatkowe wywołania metod. Powiedzmy, że mam następującą klasę

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

Czy bardziej optymalne byłoby skopiowanie i wklejenie dwóch wierszy z mojej metody clear () do konstruktora zamiast wywoływania właściwej metody? Jeśli tak, jak dużą to robi różnicę? Co by się stało, gdyby mój konstruktor wykonał 10 wywołań metod, z których każde po prostu ustawiając zmienną instancji na wartość? Jaka jest najlepsza praktyka programistyczna?


4
Ale poczekaj, jeśli teraz wywołasz metodę, dorzucimy wywołanie metody SECOND, całkowicie za darmo! Płać tylko za wysyłkę i obsługę! Ale naprawdę. W wywołaniach metod występuje narzut, podobnie jak obciążenie związane z ładowaniem większej ilości kodu. W pewnym momencie jeden staje się droższy od drugiego. Jedynym sposobem, aby to sprawdzić, jest testowanie kodu.
Marc B

11
przedwczesne cytaty optymalizacji w 3 ... 2 ... 1

22
Nie widzę powodu, by głosować przeciw - facet zadaje całkowicie uzasadnione pytanie. Dla niektórych może to być oczywista odpowiedź, ale nie oznacza to złego pytania!
Michael Berry

3
Rzeczywiście, jest to bardzo uzasadnione pytanie, jedynym powodem głosowania negatywnego może być dokładny duplikat.
Arafangion

1
Tak, przepraszam, jeśli to oczywiste, ale uczę się sam i zajmuję się tym dopiero od kilku miesięcy. Niektóre rzeczy, które widziałem w przykładowym kodzie online, nie są dobrą praktyką, więc chcę tylko dokładnie sprawdzić.
jhlu87

Odpowiedzi:


74

Czy bardziej optymalne byłoby skopiowanie i wklejenie dwóch wierszy z mojej metody clear () do konstruktora zamiast wywoływania właściwej metody?

Kompilator może przeprowadzić tę optymalizację. Podobnie jak JVM. Terminologia używana przez twórców kompilatorów i autorów JVM to „rozwinięcie inline”.

Jeśli tak, jak dużą to robi różnicę?

Zmierz to. Często okaże się, że nie ma to znaczenia. A jeśli uważasz, że jest to popularny punkt widzenia, szukasz niewłaściwego miejsca; dlatego musisz to zmierzyć.

Co by się stało, gdyby mój konstruktor wykonał 10 wywołań metod, z których każde po prostu ustawiając zmienną instancji na wartość?

Ponownie, zależy to od wygenerowanego kodu bajtowego i wszelkich optymalizacji czasu wykonywania wykonywanych przez wirtualną maszynę Java. Jeśli kompilator / JVM może wbudować wywołania metod, przeprowadzi optymalizację, aby uniknąć narzutu związanego z tworzeniem nowych ramek stosu w czasie wykonywania.

Jaka jest najlepsza praktyka programistyczna?

Unikanie przedwczesnej optymalizacji. Najlepszą praktyką jest napisanie czytelnego i dobrze zaprojektowanego kodu, a następnie optymalizacja pod kątem punktów aktywnych wydajności w aplikacji.


jaki jest dobry sposób na test porównawczy? czy jest jakieś oprogramowanie, które mogę pobrać, czy masz na myśli po prostu użycie System.nanoTime () na początku i na końcu i wydrukowanie różnicy?
jhlu87

System.nanoTime()lub System.currentTimeMillisjest złym sposobem tworzenia profili. Możesz uzyskać listę profilerów z odpowiedzi na to pytanie Stackoverflow . Poleciłbym VisualVM, ponieważ jest teraz dostarczany z JDK.
Vineet Reynolds

2
@ jhlu87: Myślę, że bardzo trudno byłoby uzyskać dokładne oszacowanie narzutu wywołania metody. Mikroznakowanie jest bardzo trudne do wykonania, a nawet wtedy generalnie nie jest zbyt przydatne w ogólnym planie. Przeczytaj to .
ColinD

@VineetReynolds powiązane pytanie nie żyje.
dim8

19

To, co wszyscy powiedzieli o optymalizacji, jest absolutnie prawdziwe.

Z punktu widzenia wydajności nie ma powodu, aby wbudować tę metodę. Jeśli jest to problem z wydajnością, JIT w Twojej maszynie JVM wstawi go. W Javie wywołania metod są tak bliskie bezpłatnemu, że nie warto o tym myśleć.

To powiedziawszy, jest tu inny problem. Mianowicie, że jest złą praktyką programowania wywołać metodę na sterowanie ręczne (czyli taki, który nie jest final, staticlub private) z konstruktora. (Effective Java, 2nd Ed., Str. 89, pozycja zatytułowana „Projektuj i dokumentuj do dziedziczenia albo zakazuj”)

Co się stanie, jeśli ktoś doda podklasę BinarySearchTreenazwaną, LoggingBinarySearchTreektóra przesłania wszystkie publiczne metody z kodem takim jak:

public void clear(){
  this.callLog.addCall("clear");
  super.clear();
}

Wtedy LoggingBinarySearchTreenigdy nie będzie można zbudować! Problem polega na tym this.callLog, że nastąpi to, nullgdy BinarySearchTreekonstruktor jest uruchomiony, ale clearwywoływany jest ten nadpisany, a otrzymasz plik NullPointerException.

Zwróć uwagę, że Java i C ++ różnią się tutaj: w C ++ konstruktor nadklasy, który wywołuje virtualmetodę, w końcu wywołuje tę zdefiniowaną w superklasie, a nie przesłoniętą. Ludzie, którzy przełączają się między dwoma językami, czasami o tym zapominają.

Biorąc to pod uwagę, myślę, że w twoim przypadku prawdopodobnie łatwiej jest wstawić clearmetodę, gdy jest ona wywoływana z konstruktora , ale generalnie w Javie powinieneś wykonać wszystkie żądane wywołania metod.


1
Nie sądzę, żeby prosił o wskazówki dotyczące stylu kodowania, ale raczej wiedział, czy wywołanie metody jest drogie, czy nie
Asaf Mesika

3
Zapytał wprost "Jaka jest najlepsza praktyka programistyczna?" - z punktu widzenia najlepszych praktyk jest to bardzo istotne.
Daniel Martin,

2
Jeśli weźmiesz tylko ostatnie zdanie, całkowicie tracisz kontekst tego pytania. Chce wiedzieć, czy podzielenie dużej metody na wiele mniejszych metod jest kosztowne, ponieważ wywołanie metody ma swoją cenę. Dodanie odpowiedzi opisującej wzorzec anty do wywołania metody innej niż ostateczna z konstruktora nie liczy się jako odpowiedź na jego pytanie jako całość. Aha i spójrz na tytuł pytania "ile kosztuje metoda połączenia"
Asaf Mesika

6

Zdecydowanie zostawiłbym to tak, jak jest. A jeśli zmienisz clear()logikę? Byłoby niepraktyczne znalezienie wszystkich miejsc, w których skopiowałeś 2 linie kodu.


4

Ogólnie rzecz biorąc (a jako początkujący oznacza to zawsze!) Nigdy nie powinieneś dokonywać mikro-optymalizacji, takich jak ta, którą rozważasz. Zawsze przedkładaj czytelność kodu nad takie rzeczy.

Czemu? Ponieważ kompilator / hotspot wykona dla ciebie tego rodzaju optymalizacje w locie i wiele, wiele innych. Jeśli już, próbując dokonać optymalizacji według tego rodzaju linii (choć nie w tym przypadku), prawdopodobnie spowolnisz działanie. Hotspot rozumie popularne idiomy programistyczne, jeśli spróbujesz samodzielnie wykonać tę optymalizację, prawdopodobnie nie zrozumie, co próbujesz zrobić, więc nie będzie w stanie go zoptymalizować.

Koszty utrzymania są również znacznie wyższe. Jeśli zaczniesz powtarzać kod, będzie to znacznie trudniejsze w utrzymaniu, co prawdopodobnie będzie o wiele bardziej kłopotliwe, niż mogłoby się wydawać!

Nawiasem mówiąc, możesz dojść do niektórych punktów w swoim życiu kodowania, w których musisz dokonać optymalizacji na niskim poziomie - ale jeśli osiągniesz te punkty, na pewno będziesz wiedział, kiedy nadejdzie czas. A jeśli tego nie zrobisz, zawsze możesz wrócić i zoptymalizować później, jeśli zajdzie taka potrzeba.


3

Najlepszą praktyką jest dwukrotne mierzenie i jednokrotne cięcie.

Po zmarnowaniu czasu na optymalizację już nigdy nie odzyskasz jej! (Więc najpierw zmierz to i zadaj sobie pytanie, czy warto ją zoptymalizować. Ile czasu faktycznie zaoszczędzisz?)

W tym przypadku maszyna wirtualna Java prawdopodobnie przeprowadza już optymalizację, o której mówisz.


3

Koszt od wywołania metody jest stworzenie (i utylizacji) w ramce stosu i kilka dodatkowych wyrażeń kod bajtowy jeśli trzeba przekazać wartości do metody.


1

Wzorzec, który podążam, jest taki, czy ta metoda, o której mowa, spełniałaby jedną z poniższych sytuacji:

  • Czy byłoby pomocne, gdyby ta metoda była dostępna poza tą klasą?
  • Czy byłoby pomocne, gdyby ta metoda była dostępna w innych metodach?
  • Czy byłoby frustrujące przepisywanie tego za każdym razem, gdy tego potrzebowałem?
  • Czy można zwiększyć wszechstronność metody za pomocą kilku parametrów?

Jeśli którekolwiek z powyższych jest prawdą, powinno być zawarte w jego własnej metodzie.


Łatwiej jest nie zadawać tych pytań i po prostu wstawić kod do swojej własnej metody!
Arafangion

2
Pytający jest ciekawy, jaka powinna być szczegółowość wywołań jego metod. Nie ma potrzeby tworzenia metody zwiększania liczby całkowitej, jeśli możesz po prostu użyći++;
Peaches491

W rzeczywistości tworzenie metody ma dużą wartość, nawet jeśli nie ma szans, aby kiedykolwiek została ponownie wykorzystana. Samo nadanie nazwy blokowi kodu i ukazanie ogólnej struktury jest wielką korzyścią samą w sobie.
Joffrey,

1

Zachowaj clear()metodę, jeśli pomaga to w czytelności. Posiadanie niemożliwego do utrzymania kodu jest droższe.


1

Optymalizacja kompilatorów zwykle całkiem nieźle radzi sobie z usuwaniem nadmiarowości z tych „dodatkowych” operacji; w wielu przypadkach różnica między „zoptymalizowanym” kodem a kodem po prostu napisanym tak, jak chcesz, i uruchomionym przez optymalizujący kompilator jest żadna; to znaczy, optymalizujący kompilator zwykle wykonuje tak dobrą robotę, jak ty, i robi to bez powodowania degradacji kodu źródłowego. W rzeczywistości, kod „ręcznie zoptymalizowany” często okazuje się MNIEJ wydajny, ponieważ kompilator bierze pod uwagę wiele rzeczy podczas optymalizacji. Zostaw swój kod w czytelnym formacie i nie martw się optymalizacją aż do później.

„Przedwczesna optymalizacja jest źródłem wszelkiego zła”. - Donald Knuth


0

Nie martwiłbym się tak bardzo wywołaniem metody, ale logiką metody. Gdyby to były systemy krytyczne, a system musiałby „działać szybko”, przyjrzałbym się optymalizacji kodów, których wykonanie zajmuje dużo czasu.


0

Biorąc pod uwagę pamięć nowoczesnych komputerów, jest to bardzo niedrogie. Zawsze lepiej jest rozbić kod na metody, aby ktoś mógł szybko przeczytać, co się dzieje. Pomoże również w zawężeniu liczby błędów w kodzie, jeśli błąd jest ograniczony do jednej metody z treścią kilku wierszy.


0

Jak powiedzieli inni, koszt wywołania metody jest trywialny do nada, ponieważ kompilator zoptymalizuje go dla Ciebie.

To powiedziawszy, istnieją niebezpieczeństwa związane z wywoływaniem metod do metod instancji z konstruktora. Istnieje ryzyko późniejszej aktualizacji metody instancji, tak aby mogła ona próbować użyć zmiennej instancji, która nie została jeszcze zainicjowana przez konstruktora. Oznacza to, że niekoniecznie chcesz oddzielić czynności konstrukcyjne od konstruktora.

Kolejne pytanie - twoja metoda clear () ustawia root na EMPTY, który jest inicjowany podczas tworzenia obiektu. Jeśli następnie dodasz węzły do ​​EMPTY, a następnie wywołasz funkcję clear (), nie będziesz resetować węzła głównego. Czy takiego zachowania chcesz?

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.