Nie, ale tak, ale może, ale może na odwrót, ale nie.
Jak już zauważyli ludzie (zakładając język, w którym dodawanie jest lewostronne, takie jak C, C ++, C # lub Java), wyrażenie ((1 + 2) + 3)
jest dokładnie równoważne z 1 + 2 + 3
. Są różne sposoby pisania czegoś w kodzie źródłowym, co miałoby zerowy wpływ na wynikowy kod maszynowy lub kod bajtowy.
Tak czy inaczej, wynikiem będzie instrukcja np. Dodania dwóch rejestrów, a następnie dodania trzeciego lub pobrania dwóch wartości ze stosu, dodania go, wypchnięcia z powrotem, a następnie wzięcia go i dodania innych, lub dodania trzech rejestrów w pojedyncza operacja lub inny sposób zsumowania trzech liczb w zależności od tego, co jest najbardziej sensowne na następnym poziomie (kod maszynowy lub kod bajtowy). W przypadku kodu bajtowego to z kolei prawdopodobnie ulegnie podobnej przebudowie w tym, że np. Równoważnik IL tego (który byłby serią ładunków do stosu, i popping par, aby dodać, a następnie odepchnąć wynik) nie spowodowałoby bezpośredniej kopii tej logiki na poziomie kodu maszynowego, ale coś bardziej sensownego dla danej maszyny.
Ale w twoim pytaniu jest coś więcej.
W przypadku każdego rozsądnego kompilatora C, C ++, Java lub C # spodziewałbym się, że wyniki obu podanych instrukcji będą miały dokładnie takie same wyniki jak:
int a = 6;
Dlaczego wynikowy kod miałby marnować czas na matematykę literałów? Żadne zmiany w stanie programu nie zatrzymają wyniku 1 + 2 + 3
bycia 6
, więc to powinno być w wykonywanym kodzie. Rzeczywiście, może nawet nie to (w zależności od tego, co zrobisz z tym 6, może uda nam się wyrzucić całość; a nawet C # z jego filozofią „nie optymalizuj mocno, ponieważ jitter i tak to zoptymalizuje” albo wytworzy ekwiwalent int a = 6
lub po prostu wyrzuć to wszystko jako niepotrzebne).
To jednak prowadzi nas do możliwego rozszerzenia twojego pytania. Rozważ następujące:
int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;
i
int c;
if(d < 100)
c = 0;
else
c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);
(Uwaga: te dwa ostatnie przykłady nie są poprawne w C #, podczas gdy wszystko inne tutaj jest, i są poprawne w C, C ++ i Java.)
Tutaj znowu mamy dokładnie równoważny kod pod względem wydajności. Ponieważ nie są to wyrażenia stałe, nie będą obliczane w czasie kompilacji. Możliwe, że jedna forma jest szybsza od drugiej. Który jest szybszy? Zależy to od procesora i być może od pewnych raczej dowolnych różnic w stanie (zwłaszcza, że jeśli ktoś jest szybszy, prawdopodobnie nie będzie dużo szybszy).
I nie są one całkowicie niezwiązane z twoim pytaniem, ponieważ dotyczą głównie różnic w kolejności, w jakiej coś jest koncepcyjnie zrobione.
W każdym z nich można podejrzewać, że jedno może być szybsze od drugiego. Pojedyncze dekrementy mogą mieć wyspecjalizowane instrukcje, więc (b / 2)--
rzeczywiście mogą być szybsze niż (b - 2) / 2
. d * 32
być może można by go wyprodukować szybciej, zmieniając go w d << 5
taki sposób, aby był d * 32 - d
szybszy niż d * 31
. Różnice między dwoma ostatnimi są szczególnie interesujące; jeden pozwala w niektórych przypadkach na pominięcie przetwarzania, ale drugi pozwala uniknąć błędnego przewidywania gałęzi.
Pozostaje nam zatem dwa pytania: 1. Czy jedno jest rzeczywiście szybsze od drugiego? 2. Czy kompilator przekształci wolniejszy w szybszy?
A odpowiedź brzmi 1. To zależy. 2. Może
Lub, aby rozwinąć, zależy to, ponieważ zależy od danego procesora. Z pewnością istniały procesory, w których naiwny ekwiwalent kodu maszynowego jednego byłby szybszy niż naiwny ekwiwalent kodu maszynowego drugiego. W ciągu historii komputerów elektronicznych nie było też takiego, który byłby zawsze szybszy (element przewidywania błędnych rozgałęzień nie był szczególnie istotny dla wielu, gdy niepoprawne procesory były częstsze).
A może dlatego, że istnieje wiele różnych optymalizacji, które wykonają kompilatory (i fluktuacje i silniki skryptów), i chociaż niektóre mogą być wymagane w niektórych przypadkach, zazwyczaj będziemy w stanie znaleźć niektóre logicznie równoważne kody, które nawet najbardziej naiwny kompilator ma dokładnie takie same wyniki i niektóre logicznie równoważne kody, w których nawet najbardziej wyrafinowany produkuje szybszy kod dla jednego niż dla drugiego (nawet jeśli musimy napisać coś całkowicie patologicznego, aby udowodnić swój punkt).
To może wydawać się bardzo małym problemem optymalizacji,
Nie. Nawet przy bardziej skomplikowanych różnicach niż te, które tu przedstawiam, wydaje się to absolutnie drobiazgową troską, która nie ma nic wspólnego z optymalizacją. Jeśli tak, to kwestia pesymizacji, ponieważ podejrzewasz, że trudniejsze do odczytania ((1 + 2) + 3
może być wolniejsze niż łatwiejsze do odczytania 1 + 2 + 3
.
ale wybranie C ++ zamiast C # / Java / ... polega na optymalizacji (IMHO).
Jeśli o to właśnie chodziło o wybranie C ++ zamiast C # lub Javy, powiedziałbym, że ludzie powinni wypalić swoją kopię Stroustrup i ISO / IEC 14882 i zwolnić miejsce na kompilatorze C ++, aby zostawić miejsce na więcej plików MP3 lub coś w tym rodzaju.
Te języki mają różne zalety względem siebie.
Jedną z nich jest to, że C ++ jest generalnie szybszy i lżejszy pod względem zużycia pamięci. Tak, istnieją przykłady, w których C # i / lub Java są szybsze i / lub mają lepsze wykorzystanie pamięci przez cały okres użytkowania aplikacji, i stają się one coraz powszechniejsze wraz z poprawą technologii, ale nadal możemy spodziewać się, że przeciętny program napisany w C ++ będzie mniejszy plik wykonywalny, który działa szybciej i zużywa mniej pamięci niż odpowiednik w jednym z tych dwóch języków.
To nie jest optymalizacja.
OptymalizacjaCzasami oznacza „przyspieszenie”. Jest to zrozumiałe, ponieważ często, gdy naprawdę mówimy o „optymalizacji”, naprawdę mówimy o przyspieszeniu, a więc jedno stało się skrótem dla drugiego i przyznam, że sam niewłaściwie używam tego słowa.
Prawidłowe słowo „przyspieszanie” nie oznacza optymalizacji . Prawidłowe słowo to poprawa . Jeśli zmienisz program, a jedyną znaczącą różnicą jest to, że jest on teraz szybszy, nie jest w żaden sposób zoptymalizowany, jest po prostu lepszy.
Optymalizacja polega na wprowadzeniu ulepszeń w odniesieniu do konkretnego aspektu i / lub konkretnego przypadku. Typowe przykłady to:
- Jest teraz szybszy dla jednego przypadku użycia, ale wolniejszy dla innego.
- Jest teraz szybszy, ale zużywa więcej pamięci.
- Jest teraz lżejszy w pamięci, ale wolniejszy.
- Jest teraz szybszy, ale trudniejszy w utrzymaniu.
- Teraz jest łatwiejszy w utrzymaniu, ale wolniejszy.
Takie przypadki byłyby uzasadnione, jeśli np .:
- Szybszy przypadek użycia jest na początku bardziej powszechny lub poważniej utrudniony.
- Program był niedopuszczalnie wolny i mamy dużo wolnej pamięci RAM.
- Program się zatrzymał, ponieważ zużywał tak dużo pamięci RAM, że spędzał więcej czasu na zamianie niż na wykonywanie superszybkiego przetwarzania.
- Program był niedopuszczalnie wolny, a trudniejszy do zrozumienia kod jest dobrze udokumentowany i względnie stabilny.
- Program jest wciąż akceptowalnie szybki, a bardziej zrozumiała baza kodu jest tańsza w utrzymaniu i pozwala na łatwiejsze wprowadzanie innych ulepszeń.
Ale takie przypadki nie byłyby również uzasadnione w innych scenariuszach: kod nie został ulepszony przez absolutną nieomylną miarę jakości, został ulepszony pod szczególnym względem, co czyni go bardziej odpowiednim do określonego zastosowania; zoptymalizowany.
Wybór języka ma tutaj wpływ, ponieważ może to mieć wpływ na szybkość, zużycie pamięci i czytelność, ale może to również wpływać na kompatybilność z innymi systemami, dostępność bibliotek, dostępność środowisk uruchomieniowych, dojrzałość tych środowisk uruchomieniowych w danym systemie operacyjnym (z powodu moich grzechów w pewnym sensie skończyłem z Linuksem i Androidem jako moim ulubionym systemem operacyjnym i C # jako moim ulubionym językiem, i chociaż Mono jest świetny, ale nadal dość często go spotykam).
Powiedzenie „wybór C ++ zamiast C # / Java / ... dotyczy optymalizacji” ma sens tylko wtedy, gdy uważasz, że C ++ jest do bani, ponieważ optymalizacja polega na „lepszym, pomimo…”, a nie „lepszym”. Jeśli uważasz, że C ++ jest lepszy mimo wszystko, to ostatnią rzeczą, której potrzebujesz, jest martwienie się o tak małe możliwe mikroopty. Rzeczywiście, prawdopodobnie lepiej w ogóle porzucić to; happy hakerzy to także jakość, którą można zoptymalizować!
Jeśli jednak skłaniasz się do powiedzenia „Kocham C ++, a jedną z rzeczy, które kocham w tym jest wyciskanie dodatkowych cykli”, to jest inna sprawa. Wciąż jest tak, że mikroopty są tego warte tylko wtedy, gdy mogą być nawykiem zwrotnym (tzn. Sposób, w jaki kodujesz w naturalny sposób, będzie szybszy niż wolniejszy). W przeciwnym razie nie są nawet przedwczesną optymalizacją, są przedwczesną pesymizacją, która tylko pogarsza sytuację.