Czy programowanie modułowe wpływa na czas obliczeń?

19

Wszyscy mówią, że powinienem uczynić mój kod modułowym, ale czy nie jest to mniej wydajne, jeśli używam więcej wywołań metod niż mniejszych, ale większych metod? Jaka jest różnica w Javie, C lub C ++ pod tym względem?

Rozumiem, że łatwiej jest edytować, czytać i rozumieć, szczególnie w grupie. Czy zatem utrata czasu obliczeń jest nieznaczna w porównaniu z korzyściami związanymi z utrzymaniem czystości kodu?

fatsokol
źródło
2
Pytanie brzmi, ile czasu zajmie zaoszczędzony czas przetwarzania, który poświęcisz na trudniejszą konserwację. Odpowiedź na to zależy całkowicie od twojej aplikacji.
Blrfl
2
Wiele dobrych pytań generuje pewien stopień opinii w oparciu o doświadczenie ekspertów, ale odpowiedzi na to pytanie będą zwykle prawie w całości oparte na opiniach, a nie na faktach, referencjach lub konkretnej wiedzy specjalistycznej.
komara
10
Warto również zauważyć, że kara obliczeniowa za wywołanie funkcji lub metody jest tak mała, że ​​nawet w bardzo dużym programie z dużą ilością funkcji i wywołań metod, wykonywanie tych wywołań nie zajmuje nawet miejsca na wykresie.
greyfade
1
@greyfade: Tak jest w przypadku skoków bezpośrednich, ale dodatkowy skok przewidywany pośrednio może kosztować na przykład ~ 3% całkowitego czasu działania programu (tylko część z programu, który ostatnio sprawdziłem - choć może nie był reprezentatywny). W zależności od obszaru możesz, ale nie musi, uznać go za znaczący, ale zarejestrował się na wykresie (i oczywiście jest przynajmniej częściowo ortogonalny względem modułowości).
Maciej Piechotka
4
Przedwczesna optymalizacja jest źródłem wszelkiego zła. Kod liniowy jest nieco szybszy niż kod modułowy. Kod modułowy jest znacznie szybszy niż kod spaghetti. Jeśli dążysz do kodu liniowego bez bardzo (BARDZO) dokładnego projektu całości, skończysz z kodem spaghetti, gwarantuję to.
SF.

Odpowiedzi:

46

Tak, to nie ma znaczenia.

Komputery to niestrudzone, prawie perfekcyjne silniki wykonawcze pracujące z prędkościami zupełnie nieporównywalnymi do mózgów. Chociaż istnieje wymierna ilość czasu, którą wywołanie funkcji wydłuża czas wykonywania programu, jest to niczym w porównaniu z dodatkowym czasem potrzebnym mózgowi następnej osoby związanej z kodem, gdy muszą one rozplątać nieczytelną procedurę nawet zacząć rozumieć, jak z tym pracować. Możesz wypróbować obliczenia żartu - załóż, że Twój kod musi zostać zachowany tylko raz , a to tylko dodaje pół godziny do czasu potrzebnego na pogodzenie się z kodem. Weź pod uwagę szybkość zegara procesora i oblicz: ile razy kod musiałby biec, żeby nawet marzyć o wyrównaniu?

Krótko mówiąc, litowanie się nad procesorem jest całkowicie błędne w 99,99% przypadków. W rzadkich pozostałych przypadkach użyj profilerów. Czy nie zakładają, że można na miejscu tych przypadków - nie można.

Kilian Foth
źródło
2
Chociaż zgadzam się, że przez większość czasu jest to przedwczesna optymalizacja, myślę, że twój argument dotyczący czasu jest zły. Czas jest względny w różnych sytuacjach i nie możesz po prostu obliczyć tak, jak zrobiłeś.
Honza Brabec
28
+1 tylko dla wyrażenia „litowanie się nad procesorem”, ponieważ tak ładnie akcentuje błędność w przedwczesnej optymalizacji.
Michael Borgwardt
4
Wysoce powiązane: jak szybkie są komputery na co dzień? Zejście z drogi, aby uniknąć kilku wywołań funkcji „prędkość”, jest jak zejście z drogi, aby zaoszczędzić komuś jedną minutę w ciągu całego życia . Krótko mówiąc: nie warto nawet brać pod uwagę czasu.
BlueRaja - Danny Pflughoeft
7
+1 za tak wymowne sprzedawanie tego, czego brakuje wielu, ale zapomniałeś dodać najważniejszą wagę do równowagi, która sprawia, że ​​litowanie się nad procesorem jest jeszcze poważniejsze: Pomijając utracone pieniądze i czas poświęcony na tę jednorazową konserwację; jeśli zajęło to 1 sekundę, nadal jest tam o wiele bardziej podstępna umywalka. Ryzyko błędów . Im bardziej kłopotliwy i trudny jest dla późniejszego opiekuna zmiana twojego kodu, tym większe ryzyko, że wdroży w nim błędy, co może spowodować niewiarygodnie duże nieuniknione koszty z tytułu ryzyka dla użytkowników, a wszelkie błędy spowodują późniejszą konserwację (co może powodować błędy ...)
Jimmy Hoffa
Mój stary nauczyciel zwykł mawiać: „Jeśli zastanawiasz się, czy przedwcześnie optymalizujesz, czy nie, zatrzymaj się tutaj. Gdyby to był właściwy wybór, wiedziałbyś o tym”.
Ven
22

To zależy.

W lodowato wolnym świecie, jakim jest programowanie sieciowe, w którym wszystko dzieje się z ludzką prędkością, programowanie wymagające metod, w którym koszt wywołania metody jest porównywalny lub przekracza koszt przetwarzania wykonanego metodą, prawdopodobnie nie ma znaczenia .

W świecie programowania systemów wbudowanych i programów obsługi przerwań dla przerwań o dużej szybkości, z pewnością ma to znaczenie. W takim środowisku psują się zwykłe modele „dostępu do pamięci” i „procesora jest nieskończenie szybki”. Widziałem, co się dzieje, gdy zorientowany obiektowo programista na komputerze mainframe pisze swój pierwszy szybki program obsługi przerwań. To nie było ładne.

Kilka lat temu robiłem nierekurencyjne 8-kierunkowe barwienie kropelek łączności na zdjęciach FLIR w czasie rzeczywistym, na czym był wtedy przyzwoity procesor. W pierwszej próbie wykorzystano wywołanie podprogramu, a narzut wywołany podprogramem zjadł procesor żywy. (4 wywołania NA PIKSEL x 64 000 pikseli na klatkę x 30 klatek na sekundę = to rozgryzłeś). Druga próba zmieniła podprogram na makro C, bez utraty czytelności, i wszystko było jak róże.

Musisz ciężko patrzeć na to, co robisz i na środowisko, w którym będziesz to robić.

John R. Strohm
źródło
Pamiętaj, że większość współczesnych problemów programistycznych najlepiej radzić sobie z dużą ilością zorientowanego obiektowo, zgodnego z metodami kodu. Będziesz wiedział, kiedy jesteś w środowisku systemów wbudowanych.
Kevin - Przywróć Monikę
4
+1, ale z zastrzeżeniem: zwykle jest tak, że kompilator optymalizujący często lepiej wykonuje wstawianie, rozwijanie pętli i optymalizacje skalarne i wektorowe niż osoba. Programista musi bardzo dobrze znać język, kompilator i maszynę, aby z tego skorzystać, więc nie jest to magia.
detly
2
To prawda, ale zoptymalizowałeś kod po napisaniu logiki i jej profilowaniu, aby znaleźć problematyczną część algorytmu. Nie zacząłeś kodować dla wydajności, ale dla czytelności. Makra pomogą ci zachować czytelność kodu.
Uwe Plonus
2
Nawet w programowaniu systemów wbudowanych, zacznij od napisania czytelnego poprawnego kodu i zacznij optymalizację dopiero po tym, jeśli to konieczne i zgodnie z profilowaniem . W przeciwnym razie zbyt łatwo jest włożyć dużo pracy, co nie ma realnego wpływu na wydajność / rozmiar kodu, a po prostu utrudnia zrozumienie źródła.
Donal Fellows
1
@detly: Tak i nie. Nowoczesne kompilatory OGÓLNIE wykonują lepszą pracę W RAMACH OGRANICZEŃ JĘZYKOWYCH. Ludzki programista wie, czy ograniczenia narzucone przez język obowiązują w danym przypadku. Na przykład, kompilatory Cn i C ++ są wymagane przez standardy językowe, aby umożliwić programiście wykonanie pewnych bardzo patologicznych rzeczy i wygenerowanie kodu, który i tak działa. Programista może wiedzieć, że nie jest na tyle szalony, głupi ani żądny przygód, aby robić te rzeczy, i dokonywać optymalizacji, które w innym przypadku byłyby niebezpieczne, ponieważ wie, że w tym przypadku są bezpieczne.
John R. Strohm,
11

Po pierwsze: programy w wyższym języku są przeznaczone do czytania przez ludzi, a nie przez maszyny.

Napisz więc programy, aby je zrozumieć. Nie myśl o wydajności (jeśli poważnie masz problemy z wydajnością, profiluj swoją aplikację i zwiększ wydajność tam, gdzie jest to potrzebne).

Nawet jeśli prawdą jest, że wywołanie metody lub funkcji wymaga pewnego narzutu, nie ma to znaczenia. Dzisiaj kompilatory powinny być w stanie skompilować kod do wydajnego języka maszynowego, aby wygenerowany kod był wydajny dla architektury docelowej. Użyj przełączników optymalizacyjnych kompilatora, aby uzyskać wydajny kod.

Uwe Plonus
źródło
5
Powiedziałbym, że powinniśmy pisać programy w taki sposób, aby inni je zrozumieli.
Bartłomiej Lewandowski
Pierwszą osobą, która musi przeczytać program, jest sam programista. To jest powód, dlaczego napisałem ci . Jeśli inne osoby potrafią czytać program, to też miło, ale (w moich oczach) nie jest to konieczne (w pierwszej kolejności). Jeśli pracujesz w zespole, inne osoby również powinny zrozumieć program, ale moim zamiarem było, aby ta osoba przeczytała program, a nie komputery.
Uwe Plonus
5

Zazwyczaj, gdy w innym przypadku miałbyś dużą funkcję i podzieliłeś ją na wiele mniejszych, te mniejsze będą wstawiane, ponieważ jedyny minus inlinizacji (zbyt duże powtarzanie tych samych instrukcji) nie ma znaczenia w tym przypadku. Oznacza to, że Twój kod będzie działał tak, jakbyś napisał jedną dużą funkcję.

Jeśli z jakiegoś powodu nie są one wstawiane, a to staje się problemem z wydajnością, należy rozważyć ręczne wstawianie. Nie wszystkie aplikacje są połączonymi w sieć formularzami CRUD z ogromnymi wewnętrznymi opóźnieniami.

Esailija
źródło
2

Prawdopodobnie nie ma kosztów obliczeniowych. Zwykle kompilatory / zespoły JIT z ostatnich 10-20 lat zajmują się funkcją wprowadzającą doskonale. W przypadku C / C ++ zwykle ogranicza się do funkcji „nieuniknionych” (tzn. Definicja kompilatora jest dostępna podczas kompilacji - to jest w nagłówku tego samego pliku), ale obecne techniki LTO przezwyciężyły to.

To, czy powinieneś poświęcić czas na optymalizację, zależy od obszaru, nad którym pracujesz. Jeśli masz do czynienia z „normalną” aplikacją, która przez większość czasu czekała na dane wejściowe - prawdopodobnie nie powinieneś się martwić optymalizacjami, chyba że aplikacja „poczuje się” powolna.

Nawet w takich przypadkach powinieneś skoncentrować się na wielu rzeczach przed wykonaniem mikrooptymalizacji:

  • Gdzie są problemy? Zwykle ludzie mają trudności ze znalezieniem hotspotów, ponieważ czytamy kod źródłowy inaczej. Mamy inny stosunek czasu działania i wykonujemy je sekwencyjnie, podczas gdy nowoczesne procesory tego nie robią .
  • Czy za każdym razem potrzebne są jakieś obliczenia? Na przykład, jeśli zmienisz pojedynczy parametr spośród tysięcy, możesz chcieć obliczyć tylko część, której dotyczy problem, zamiast całego modelu.
  • Czy używasz optymalnego algorytmu? Zmiana z O(n)na O(log n)może mieć znacznie większy wpływ niż wszystko, co można osiągnąć dzięki mikrooptymalizacji.
  • Czy używasz odpowiednich struktur? Załóżmy, że używasz, Listkiedy potrzebujesz, HashSetwięc możesz O(n)wyszukiwać, kiedy możesz O(1).
  • Czy efektywnie wykorzystujesz równoległość? Obecnie nawet telefony komórkowe mogą mieć 4 rdzenie lub więcej, może być kuszące, aby używać wątków. Jednak nie są srebrną kulą, ponieważ mają koszt synchronizacji (nie wspominając, że jeśli problem jest związany z pamięcią, i tak nie ma sensu).

Nawet jeśli zdecydujesz, że musisz przeprowadzić mikrooptymalizację (co praktycznie oznacza, że ​​twoje oprogramowanie jest używane w HPC, wbudowane lub używane przez bardzo dużą liczbę osób - w przeciwnym razie dodatkowe koszty utrzymania przezwyciężyłyby koszty czasu komputera), potrzebujesz aby zidentyfikować punkty aktywne (jądra), które chcesz przyspieszyć. Ale prawdopodobnie powinieneś:

  1. Poznaj dokładnie platformę, nad którą pracujesz
  2. Wiedz dokładnie, na jakim kompilatorze pracujesz i jaką optymalizację może on wykonać oraz jak napisać kod idiomatyczny, aby umożliwić tę optymalizację
  3. Pomyśl o wzorcach dostępu do pamięci i ile możesz zmieścić w pamięci podręcznej (dokładny rozmiar, który znasz z punktu 1).
  4. Jeśli więc jesteś związany obliczeniami, pomyśl o reorganizacji obliczeń, aby zapisać obliczenia

Jako ostatnia uwaga. Zwykle jedynym problemem związanym z wywołaniami metod są skoki pośrednie (metody wirtualne), które nie były przewidywane przez predyktor gałęzi (niestety skok pośredni jest trudnym przypadkiem). Jednak:

  • Java ma JIT, który w wielu przypadkach może przewidzieć typ klasy, a zatem cel skoku wcześniej, a zatem w hotspotach nie powinieneś mieć wielu problemów.
  • Kompilatory C ++ często przeprowadzają analizę programu i przynajmniej w niektórych przypadkach mogą przewidzieć cel w czasie kompilacji.
  • W obu przypadkach, gdy cel został przewidziany, wstawianie powinno działać. Jeśli kompilator nie byłby w stanie wykonać inliniowych szans, my też nie moglibyśmy.
Maciej Piechotka
źródło
0

Moja odpowiedź prawdopodobnie nie rozszerzy się zbytnio na istniejące odpowiedzi, ale wydaje mi się, że moje dwa centy mogą być pomocne.

Po pierwsze; tak, w przypadku modułowości zwykle rezygnujesz z pewnego poziomu czasu wykonywania. Pisanie wszystkiego w kodzie asemblera da ci najlepszą prędkość. To mówi...

Znasz YouTube? Prawdopodobnie najbardziej istniejąca witryna o dużej przepustowości lub druga po Netflix? Piszą dużą część swojego kodu w języku Python, który jest wysoce modułowym językiem, który nie został zbudowany w celu uzyskania najwyższej wydajności.

Chodzi o to, że gdy coś idzie nie tak, a użytkownicy narzekają na powolne ładowanie filmów, nie ma wielu scenariuszy, w których ta powolność byłaby ostatecznie przypisywana niskiej szybkości wykonywania Pythona. Jednak szybka ponowna kompilacja Pythona i jego modułowa zdolność do wypróbowywania nowych rzeczy bez sprawdzania typu prawdopodobnie zapewnią inżynierom szybkie debugowanie tego, co się dzieje źle („Wow. Nasz nowy stażysta napisał pętlę wykonującą nowe zapytanie podrzędne SQL dla KAŻDEGO wyniku. ”) lub („ Och, Firefox wycofał ten stary format nagłówka pamięci podręcznej i utworzył bibliotekę Python, aby łatwo skonfigurować nowy ”)

W tym sensie, nawet pod względem czasu wykonania, język modułowy może być uważany za szybszy, ponieważ gdy znajdziesz swoje wąskie gardła, prawdopodobnie łatwiej będzie zreorganizować kod, aby działał w najlepszy sposób. Tak wielu inżynierów powie ci, że hity o wysokiej wydajności nie były tam, gdzie ich zdaniem byłyby (i w rzeczywistości rzeczy, które zoptymalizowali, nie były potrzebne; lub nawet nie działały tak, jak powinny!)

Katana314
źródło
0

Tak i nie. Jak inni zauważyli program najpierw dla czytelności, potem dla wydajności. Istnieją jednak standardowe praktyki, które są zarówno czytelne, jak i efektywne. Większość kodu jest uruchamiana dość rzadko, a optymalizacja go i tak nie przyniesie większych korzyści.

Java może wstawiać mniejsze wywołania funkcji, więc nie ma powodu, aby unikać pisania funkcji. Optymalizatory zwykle działają lepiej z prostszym, łatwiejszym do odczytania kodem. Istnieją badania, które pokazują skróty, które teoretycznie powinny działać szybciej, w rzeczywistości dłużej. Kompilator JIT prawdopodobnie będzie działał lepiej, kod jest mniejszy, a często uruchamiane elementy można zidentyfikować i zoptymalizować. Nie próbowałem tego, ale spodziewałbym się, że jedna duża funkcja, która jest stosunkowo rzadko nazywana, aby się nie kompilować.

Prawdopodobnie nie dotyczy to Javy, ale w jednym badaniu stwierdzono, że większe funkcje faktycznie działały wolniej, ponieważ wymagały innego modelu odniesienia pamięci. To było specyficzne dla sprzętu i optymalizatora. W przypadku mniejszych modułów użyto instrukcji, które działały na stronie pamięci. Były one szybsze i mniejsze niż instrukcje wymagane, gdy funkcja nie pasowała do strony.

Są przypadki, w których warto zoptymalizować kod, ale na ogół trzeba profilować kod, aby ustalić, gdzie to jest. Uważam, że często nie jest to oczekiwany kod.

BillThor
źródło