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?
java
performance
methods
call
jhlu87
źródło
źródło
Odpowiedzi:
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”.
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ć.
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.
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.
źródło
System.nanoTime()
lubSystem.currentTimeMillis
jest 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.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
,static
lubprivate
) 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ę
BinarySearchTree
nazwaną,LoggingBinarySearchTree
która przesłania wszystkie publiczne metody z kodem takim jak:public void clear(){ this.callLog.addCall("clear"); super.clear(); }
Wtedy
LoggingBinarySearchTree
nigdy nie będzie można zbudować! Problem polega na tymthis.callLog
, że nastąpi to,null
gdyBinarySearchTree
konstruktor jest uruchomiony, aleclear
wywoływany jest ten nadpisany, a otrzymasz plikNullPointerException
.Zwróć uwagę, że Java i C ++ różnią się tutaj: w C ++ konstruktor nadklasy, który wywołuje
virtual
metodę, 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ć
clear
metodę, gdy jest ona wywoływana z konstruktora , ale generalnie w Javie powinieneś wykonać wszystkie żądane wywołania metod.źródło
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.źródło
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.
źródło
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.
źródło
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.
źródło
Wzorzec, który podążam, jest taki, czy ta metoda, o której mowa, spełniałaby jedną z poniższych sytuacji:
Jeśli którekolwiek z powyższych jest prawdą, powinno być zawarte w jego własnej metodzie.
źródło
i++;
Zachowaj
clear()
metodę, jeśli pomaga to w czytelności. Posiadanie niemożliwego do utrzymania kodu jest droższe.źródło
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.
źródło
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.
źródło
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.
źródło
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?
źródło