Czy istnieje różnica w wydajności między i ++ a ++ i w C?

Odpowiedzi:

396

Streszczenie: Nie

i++może potencjalnie być wolniejszy niż ++i, ponieważ stara wartośći może być konieczne zapisanie do późniejszego użycia, ale w praktyce wszystkie nowoczesne kompilatory zoptymalizują to.

Możemy to zademonstrować, patrząc na kod tej funkcji, zarówno za pomocą, jak ++ii i++.

$ cat i++.c
extern void g(int i);
void f()
{
    int i;

    for (i = 0; i < 100; i++)
        g(i);

}

Pliki są takie same, z wyjątkiem ++ii i++:

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

Skompilujemy je, a także uzyskamy wygenerowany asembler:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

Widzimy, że zarówno wygenerowany plik obiektu, jak i plik asemblera są takie same.

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
Mark Harrison
źródło
9
Wiem, że to pytanie dotyczy C, ale chciałbym wiedzieć, czy przeglądarki mogą wykonać tę optymalizację dla javascript.
TM.
69
Tak więc „Nie” odnosi się do jednego kompilatora, z którym testowałeś.
Andreas
173
@Andreas: Dobre pytanie. Kiedyś byłem pisarzem kompilatorów i miałem okazję przetestować ten kod na wielu procesorach, systemach operacyjnych i kompilatorach. Jedynym kompilatorem, który znalazłem, który nie zoptymalizował przypadku i ++ (w rzeczywistości kompilatorem, który zwrócił na to moją uwagę profesjonalnie) był kompilator Software Toolworks C80 autorstwa Walta Bilofsky'ego. Ten kompilator był przeznaczony dla systemów Intel 8080 CP / M. Można śmiało powiedzieć, że żaden kompilator, który nie zawiera tej optymalizacji, nie jest przeznaczony do ogólnego użytku.
Mark Harrison
22
Nawet jeśli różnica wydajności jest nieistotna i zoptymalizowane w wielu przypadkach - należy wziąć pod uwagę, że jest to nadal dobra praktyka, aby używać ++izamiast i++. Nie ma absolutnie żadnego powodu, aby tego nie robić, a jeśli oprogramowanie kiedykolwiek przejdzie przez łańcuch narzędzi, który go nie optymalizuje, oprogramowanie będzie bardziej wydajne. Biorąc pod uwagę, że jest tak samo łatwe do pisania, ++ijak i do pisania i++, tak naprawdę nie ma wymówki, aby nie używać ++iw ogóle .
monokrome
7
@monokrome Ponieważ programiści mogą zapewnić własną implementację operatorów prefiksów i postfiksów w wielu językach, może to nie zawsze być akceptowalnym rozwiązaniem dla kompilatora bez uprzedniego porównania tych funkcji, co może nie być trywialne.
pickypg
112

Od efektywności do intencji Andrew Koeniga:

Po pierwsze, nie jest oczywiste, że ++ijest bardziej wydajny niż i++, przynajmniej w przypadku zmiennych całkowitych.

I :

Pytanie, które należy zadać, to nie to, która z tych dwóch operacji jest szybsza, ale która z tych dwóch operacji dokładniej wyraża to, co próbujesz osiągnąć. Twierdzę, że jeśli nie używasz wartości wyrażenia, nigdy nie ma powodu, aby używać i++zamiast tego ++i, ponieważ nigdy nie ma powodu, aby kopiować wartość zmiennej, zwiększać zmienną, a następnie wyrzucić kopię.

Tak więc, jeśli wynikowa wartość nie zostanie użyta, użyłbym ++i. Ale nie dlatego, że jest bardziej wydajny: ponieważ poprawnie określa moje zamiary.

Sébastien RoccaSerra
źródło
5
Nie zapominajmy, że inni operatorzy jednoargumentowi również są prefiksami. Myślę, że ++ i jest „semantycznym” sposobem na użycie jednoargumentowego operatora, podczas gdy i ++ istnieje, aby dopasować się do konkretnej potrzeby (ocena przed dodaniem).
TM.
9
Jeśli wynikowa wartość nie jest używana, nie ma różnicy w semantyce: tzn. Nie ma podstaw do preferowania jednej lub drugiej konstrukcji.
Eamon Nerbonne,
3
Jako czytelnik widzę różnicę. Więc jako pisarz, pokażę moją intencję, wybierając jedną z nich. Mam zwyczaj, że próbuję komunikować się ze znajomymi programistami i kolegami z drużyny za pomocą kodu :)
Sébastien RoccaSerra
11
Zgodnie z radą Koeniga koduję w i++taki sam sposób, jak ja i += nlub i = i + n, tj. W postaci docelowego obiektu czasownika , operandem docelowym po lewej stronie operatora czasownika . W przypadku nie ma prawego obiektu , ale reguła nadal obowiązuje, utrzymując cel po lewej stronie operatora czasownika . i++
David R Tribble
4
Jeśli próbujesz zwiększyć i, zarówno ++, jak i ++ poprawnie wyrażają Twoją intencję. Nie ma powodu, aby preferować jedno od drugiego. Jako czytelnik nie widzę żadnej różnicy, czy twoi koledzy są zdezorientowani, gdy widzą i ++ i myślą, może to jest literówka, a on nie chciał zwiększać i?
Dan Carter
46

Lepszą odpowiedzią jest to, że ++iczasami będzie to szybsze, ale nigdy wolniejsze.

Wydaje się, że wszyscy zakładają, że ijest to typ wbudowany, taki jak int. W takim przypadku nie będzie mierzalnej różnicy.

Jeśli jednak ijest to typ złożony, możesz znaleźć mierzalną różnicę. Dla i++was musi zrobić kopię swojej klasie przed zwiększając ją. W zależności od tego, co jest zaangażowane w kopię, może rzeczywiście być wolniejsze, ponieważ dzięki niemu ++itmożesz po prostu zwrócić ostateczną wartość.

Foo Foo::operator++()
{
  Foo oldFoo = *this; // copy existing value - could be slow
  // yadda yadda, do increment
  return oldFoo;
}

Kolejna różnica polega na tym, ++iże masz opcję zwrotu referencji zamiast wartości. Ponownie, w zależności od tego, co jest zaangażowane w tworzenie kopii obiektu, może to być wolniejsze.

Prawdziwym przykładem tego, gdzie może się to zdarzyć, byłoby użycie iteratorów. Kopiowanie iteratora raczej nie będzie wąskim gardłem w twojej aplikacji, ale nadal dobrą praktyką jest nawyk używania ++izamiasti++ gdzie nie ma to wpływu na wynik.

Andrew Grant
źródło
36
Pytanie wyraźnie stwierdza C, bez odniesienia do C ++.
Dan Cristoloveanu,
5
To (co prawda stare) pytanie dotyczyło C, a nie C ++, ale myślę, że warto również wspomnieć, że w C ++, nawet jeśli klasa implementuje operatory post-fix i pre-fix, niekoniecznie są one ze sobą powiązane. Na przykład bar ++ może inkrementować jednego elementu danych, podczas gdy bar ++ może inkrementować innego elementu danych, w tym przypadku nie miałbyś opcji, aby użyć jednego z nich, ponieważ semantyka jest inna.
Kevin
-1 Chociaż jest to dobra rada dla programistów C ++, nie odpowiada ona na pytanie, które jest oznaczone jako C w najmniejszym stopniu. W C absolutnie nie ma znaczenia, czy używasz przedrostka, czy postfiksa.
Lundin
@Pacerier Pytanie oznaczone jest tylko C i C. Dlaczego zakładasz, że nie są tym zainteresowani? Biorąc pod uwagę, jak działa SO, czy nie byłoby mądrze założyć, że interesuje ich tylko C, a nie Java, C #, COBOL lub inny język nie na temat?
Lundin
18

Krótka odpowiedź:

Nigdy nie ma żadnej różnicy między i++i++i pod względem szybkości. Dobry kompilator nie powinien generować innego kodu w obu przypadkach.

Długa odpowiedź:

W każdej innej odpowiedzi nie ma wzmianki o tym, że różnica między ++ikontrai++ ma sens tylko w wyrażeniu, które znaleziono.

W przypadku for(i=0; i<n; i++), i++jest sam w swojej wypowiedzi: istnieje punkt sekwencja przed i++i jeden jest po niej. Zatem jedynym wygenerowanym kodem maszynowym jest „wzrost io 1” i dobrze zdefiniowano, w jaki sposób jest on sekwencjonowany w stosunku do reszty programu. Więc jeśli zmienisz go na prefiks ++, nie będzie to miało najmniejszego znaczenia, nadal dostaniesz kod maszynowy „wzrost io 1”.

Różnice między ++ii i++mają znaczenie tylko w wyrażeniach takich jak array[i++] = x;versus array[++i] = x;. Niektórzy mogą argumentować i twierdzić, że postfiks będzie wolniejszy w takich operacjach, ponieważ rejestr, w którym irezyduje, musi zostać przeładowany później. Zwróć jednak uwagę, że kompilator może dowolnie zamawiać instrukcje, o ile nie „zakłóca zachowania abstrakcyjnej maszyny”, jak nazywa ją standard C.

Możesz więc założyć, że array[i++] = x;zostanie on przetłumaczony na kod maszynowy jako:

  • Wartość przechowywana iw rejestrze A.
  • Zapisz adres tablicy w rejestrze B.
  • Dodaj A i B, przechowuj wyniki w A.
  • Pod tym nowym adresem reprezentowanym przez A zapisz wartość x.
  • Wartość sklepu w wysokości i w rejestrze A // nieefektywną, ponieważ dodatkowe instrukcje tutaj, zrobiliśmy to już raz.
  • Rejestr przyrostowy A.
  • Przechowuj rejestr A w i.

kompilator mógłby równie dobrze produkować kod bardziej wydajnie, na przykład:

  • Wartość przechowywana iw rejestrze A.
  • Zapisz adres tablicy w rejestrze B.
  • Dodaj A i B, przechowuj wyniki w B.
  • Rejestr przyrostowy A.
  • Przechowuj rejestr A w i.
  • ... // reszta kodu.

Tylko dlatego, że jako programista języka C zostałeś przeszkolony, aby myśleć, że postfiks ++dzieje się na końcu, kod maszynowy nie musi być zamawiany w ten sposób.

Więc nie ma różnicy między prefiksem i postfiksem ++w C. Teraz, jako programista C, powinieneś się różnić, to ludzie, którzy niekonsekwentnie używają prefiksu w niektórych przypadkach i postfiksa w innych przypadkach, bez żadnego uzasadnienia. Sugeruje to, że nie są pewni, jak działa C lub że mają nieprawidłową znajomość języka. Jest to zawsze zły znak, co z kolei sugeruje, że podejmują inne wątpliwe decyzje w swoim programie, oparte na przesądach lub „dogmatach religijnych”.

„Prefiks ++jest zawsze szybszy” jest rzeczywiście jednym z takich fałszywych dogmatów, które są powszechne wśród niedoszłych programistów C.

Lundin
źródło
3
Nikt nie powiedział „Prefiks ++ jest zawsze szybszy”. To źle cytowane. Mówili: „Postfix ++ nigdy nie jest szybszy”.
Pacerier
2
@Pacerier Nie cytuję żadnej konkretnej osoby, a jedynie szeroko rozpowszechnione, błędne przekonanie.
Lundin,
@rbaleksandar C ++! = C.
Lundin
@rbaleksandar Pytania i odpowiedzi mówią zarówno o C ++. Który jest inny niż C, ponieważ ma przeciążanie operatora i kopiowanie konstruktorów itp.
Lundin
3
@rbaleksandar c ++ == c && ++ c! = c
sadljkfhalskdjfh
17

Biorąc list od Scotta Meyersa, bardziej skuteczny c ++ pozycja 6: Rozróżnij formy przyrostu i zmniejszenia .

Wersja przedrostka jest zawsze preferowana nad postfiksem w odniesieniu do obiektów, zwłaszcza w odniesieniu do iteratorów.

Powodem tego jest spojrzenie na wzór połączeń operatorów.

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

Patrząc na ten przykład łatwo zauważyć, że operator prefiksu zawsze będzie bardziej wydajny niż postfiks. Ze względu na potrzebę tymczasowego obiektu w użyciu postfiksa.

Właśnie dlatego, gdy widzisz przykłady wykorzystujące iteratory, zawsze używają wersji prefiksu.

Ale jak wskazujesz na int, nie ma żadnej różnicy ze względu na optymalizację kompilatora, która może mieć miejsce.

JProgrammer
źródło
4
Myślę, że jego pytanie było skierowane do C, ale w C ++ masz całkowitą rację, a ponadto C ludzie powinni to przyjąć, ponieważ mogą go również używać w C ++. Zdecydowanie zbyt często widzę, że programiści C używają składni Postfiksa ;-)
Anders Rune Jensen
-1 Chociaż jest to dobra rada dla programistów C ++, nie odpowiada ona na pytanie, które jest oznaczone jako C w najmniejszym stopniu. W C absolutnie nie ma znaczenia, czy używasz przedrostka, czy postfiksa.
Lundin
1
Przedrostek i postifx @Lundin mają znaczenie w C, zgodnie z odpowiedzią Andreasa . Nie można zakładać, że kompilator zoptymalizuje się i jest to dobra praktyka dla każdego języka, aby Alwas wolał ++ i
zamiast
@JProgrammer Nie mają znaczenia, jak na twoją odpowiedź szczerze :) Jeśli okaże się, że dają inny kod, to albo dlatego, że nie udało się włączyć optymalizacji, albo dlatego, że kompilator jest zły. W każdym razie twoja odpowiedź jest nie na temat, ponieważ pytanie dotyczyło C.
Lundin
16

Oto dodatkowa obserwacja, jeśli martwisz się mikrooptymalizacją. Pętle zmniejszające się mogą być bardziej wydajne niż pętle zwiększające (w zależności od architektury zestawu instrukcji, np. ARM), biorąc pod uwagę:

for (i = 0; i < 100; i++)

W każdej pętli będziesz mieć jedną instrukcję dla:

  1. Dodawanie 1do i.
  2. Porównaj, czy ijest mniejsza niż 100.
  3. Gałąź warunkowa, jeśli ijest mniejsza niż a 100.

Podczas gdy pętla zmniejszająca się:

for (i = 100; i != 0; i--)

Pętla będzie zawierała instrukcje dla każdego z:

  1. Zmniejszenie i, ustawienie flagi statusu rejestru procesora.
  2. Oddział warunkowy w zależności od statusu rejestru procesora ( Z==0).

Oczywiście działa to tylko przy zmniejszeniu do zera!

Zapamiętane z Podręcznika programisty systemu ARM.

tonylo
źródło
Dobry. Ale czy to nie tworzy mniej trafień w pamięci podręcznej?
AndreasT
Istnieje stara dziwna sztuczka z książek o optymalizacji kodu, łącząca przewagę zerowej gałęzi testu z przyrostowym adresem. Oto przykład w języku wysokiego poziomu (prawdopodobnie bezużytecznym, ponieważ wiele kompilatorów jest wystarczająco inteligentnych, aby zastąpić go mniej wydajnym, ale bardziej powszechnym kodem pętli): int a [N]; dla (i = -N; i; ++ i) a [N + i] + = 123;
noop
@ noop, czy mógłbyś ewentualnie opracować, do których książek dotyczących optymalizacji kodu się odwołujesz? Walczyłem o znalezienie dobrych.
mezamorphic
7
Ta odpowiedź wcale nie jest odpowiedzią na pytanie.
monokrom
@mezamorphic Dla x86 moimi źródłami są: instrukcje optymalizacji Intel, Agner Fog, Paul Hsieh, Michael Abrash.
noop
11

Nie pozwól, aby pytanie „który z nich był szybszy” było decydującym czynnikiem, którego należy użyć. Możliwe, że nigdy nie będziesz się tak przejmować, a poza tym czas czytania programisty jest znacznie droższy niż czas pracy maszyny.

Użyj tego, co jest najbardziej sensowne dla człowieka czytającego kod.

Andy Lester
źródło
3
Uważam, że niewłaściwe jest preferowanie niejasnych poprawek czytelności zamiast faktycznego wzrostu wydajności i ogólnej jasności intencji.
noop
4
Mój termin „czas czytania programisty” jest z grubsza analogiczny do „jasności intencji”. „Rzeczywisty wzrost wydajności” jest często niezmierzony, wystarczająco blisko zera, aby nazwać je zero. W przypadku OP, chyba że kod został sprofilowany w celu stwierdzenia, że ​​++ i jest wąskim gardłem, pytanie o to, który jest szybszy, jest stratą czasu i jednostek programistycznych.
Andy Lester
2
Różnica w czytelności między ++ i i ++ jest tylko kwestią osobistych preferencji, ale ++ i wyraźnie implikuje prostszą obsługę niż i ++, mimo że wynik jest równoważny dla trywialnych przypadków i prostych typów danych przy optymalizacji kompilatora. Dlatego ++ i jest dla mnie zwycięzcą, gdy określone właściwości przyrostu końcowego nie są konieczne.
noop
Mówisz to, co mówię. Ważniejsze jest pokazanie zamiaru i poprawienie czytelności niż martwienie się o „wydajność”.
Andy Lester
2
Nadal nie mogę się zgodzić. Jeśli czytelność znajduje się wyżej na liście priorytetów, być może wybór języka programowania jest niewłaściwy. Głównym celem C / C ++ jest pisanie wydajnego kodu.
noop
11

Po pierwsze: różnica między i++i ++ijest nieistotna w C.


Do szczegółów.

1. Dobrze znany problem C ++: ++ijest szybszy

W C ++ ++ijest bardziej wydajny iff ijest rodzajem obiektu z przeciążonym operatorem przyrostowym.

Dlaczego?
W ++iobiekcie obiekt jest najpierw zwiększany, a następnie może być przekazywany jako stałe odwołanie do dowolnej innej funkcji. Nie jest to możliwe, jeśli wyrażenie jest foo(i++)spowodowane tym, że teraz należy wykonać przyrost, zanim foo()zostanie wywołane, ale stara wartość musi zostać przekazana foo(). W związku z tym kompilator jest zmuszony wykonać kopię iprzed wykonaniem operatora przyrostu na oryginale. Dodatkowe wywołania konstruktora / destruktora to zła część.

Jak wspomniano powyżej, nie dotyczy to podstawowych typów.

2. Mało znany fakt: i++ może być szybszy

Jeśli nie trzeba wywoływać żadnego konstruktora / destruktora, co zawsze ma miejsce w C ++ii i++powinno być równie szybkie, prawda? Nie. Są one praktycznie tak samo szybkie, ale mogą występować niewielkie różnice, które większość innych użytkowników odpowiedziała w niewłaściwy sposób.

Jak może i++być szybciej?
Chodzi o zależności danych. Jeśli wartość musi zostać załadowana z pamięci, należy wykonać dwie kolejne operacje, zwiększając ją i wykorzystując. Za ++ipomocą należy wykonać inkrementację, zanim można będzie użyć wartości. Dzięki i++, użycie nie zależy od przyrostu, a CPU może wykonać operację użycia równolegle z operacją przyrostową. Różnica wynosi co najwyżej jeden cykl CPU, więc jest naprawdę nieistotna, ale tak jest. I jest na odwrót, niż wielu by się spodziewało.

cmaster - przywróć monikę
źródło
O twoim punkcie 2: Jeśli ++ilub i++jest używane w obrębie innego wyrażenia, zmiana między nimi zmienia semantykę wyrażenia, więc wszelkie możliwe zyski / straty wydajności nie podlegają dyskusji. Jeśli są one autonomiczne, tzn. Wynik operacji nie jest natychmiast używany, to każdy porządny kompilator skompilowałby go do tego samego, na przykład INCinstrukcji asemblera.
Shahbaz
1
@Shahbaz To całkowicie prawda, ale nie o to chodzi. 1) Chociaż semantyka są różne, zarówno i++i ++imogą być używane zamiennie w prawie każdej możliwej sytuacji poprzez dostosowanie stałych pętli po drugim, więc są one w pobliżu odpowiednika w to, co robią dla programisty. 2) Mimo że oba kompilują się do tej samej instrukcji, ich wykonanie różni się dla procesora. W przypadku i++, CPU może obliczyć przyrost równolegle do niektórych innych instrukcji, które używają tej samej wartości (procesory naprawdę to robią!), Podczas gdy z ++iCPU musi zaplanować inną instrukcję po zwiększeniu.
cmaster
4
@Shahbaz Jako przykład: if(++foo == 7) bar();i if(foo++ == 6) bar();są funkcjonalnie równoważne. Jednak drugi może być o jeden cykl szybszy, ponieważ proces porównywania i przyrostu może być obliczany równolegle. Nie chodzi o to, że ten pojedynczy cykl ma duże znaczenie, ale jest różnica.
cmaster
1
Słuszna uwaga. Stałe pokazują się często (jak <na przykład vs <=) tam, gdzie ++są zwykle używane, więc konwersja między nimi jest często łatwo możliwa.
Shahbaz
1
Podoba mi się punkt 2, ale dotyczy to tylko tej wartości, prawda? Pytanie brzmi „jeśli wynikowa wartość nie jest używana?”, Więc może być myląca.
jinawee
7

@Mark Chociaż kompilator może zoptymalizować tymczasową kopię zmiennej (na podstawie stosu), a gcc (w najnowszych wersjach) robi to, nie oznacza to, że wszystkie kompilatory zawsze to zrobią.

Właśnie przetestowałem go z kompilatorami, których używamy w naszym obecnym projekcie i 3 na 4 go nie optymalizują.

Nigdy nie zakładaj, że kompilator działa poprawnie, szczególnie jeśli możliwie szybszy, ale nigdy wolniejszy kod jest tak łatwy do odczytania.

Jeśli nie masz naprawdę głupiej implementacji jednego z operatorów w kodzie:

Alwas wolę ++ i niż i ++.

Andreas
źródło
Ciekawe ... dlaczego używasz 4 różnych kompilatorów C w jednym projekcie? A może w jednym zespole lub jednej firmie?
Lawrence Dol
3
Podczas tworzenia gier na konsole każda platforma oferuje własny kompilator / łańcuch narzędzi. W idealnym świecie moglibyśmy używać gcc / clang / llvm dla wszystkich celów, ale w tym świecie musimy pogodzić się z Microsoftem, Intelem, Metroworks, Sony itp.
Andreas
5

W C kompilator może ogólnie zoptymalizować je tak, aby były takie same, jeśli wynik nie jest używany.

Jednak w C ++, jeśli używane są inne typy, które zapewniają własne operatory ++, wersja prefiksu prawdopodobnie będzie szybsza niż wersja postfiksowa. Tak więc, jeśli nie potrzebujesz semantyki Postfiksa, lepiej użyć operatora prefiksu.

Kristopher Johnson
źródło
4

Mogę wymyślić sytuację, w której postfiks jest wolniejszy niż przyrost prefiksu:

Wyobraź sobie, że procesor z rejestrem Ajest używany jako akumulator i jest to jedyny rejestr używany w wielu instrukcjach (niektóre małe mikrokontrolery są w rzeczywistości takie).

Teraz wyobraź sobie następujący program i ich tłumaczenie na hipotetyczny zestaw:

Przyrost prefiksu:

a = ++b + c;

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

Przyrost Postfix:

a = b++ + c;

; load b
LD    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

Zwróć uwagę, w jaki sposób wartość bzostała zmuszona do ponownego załadowania. Z przyrostem prefiksu kompilator może po prostu zwiększyć wartość i kontynuować korzystanie z niej, być może unikając ponownego załadowania, ponieważ żądana wartość znajduje się już w rejestrze po zwiększeniu. Jednak z przyrostem postfiksowym kompilator musi poradzić sobie z dwiema wartościami, jedną starą, a drugą przyrostową, która, jak pokazano powyżej, powoduje jeszcze jeden dostęp do pamięci.

Oczywiście, jeśli wartość przyrostu nie jest używana, na przykład pojedyncza i++;instrukcja, kompilator może (i robi) po prostu wygenerować instrukcję przyrostu bez względu na użycie postfiksu lub prefiksu.


Na marginesie chciałbym wspomnieć, że wyrażenie, w którym istnieje wyrażenie, nie b++może zostać po prostu przekonwertowane na jedno ++bbez żadnego dodatkowego wysiłku (na przykład poprzez dodanie a - 1). Zatem porównanie tych dwóch, jeśli są częścią jakiegoś wyrażenia, nie jest tak naprawdę poprawne. Często, gdy używasz b++wyrażenia, którego nie możesz użyć ++b, więc nawet gdyby ++bbyły potencjalnie bardziej wydajne, byłoby to po prostu błędne. Wyjątek stanowi oczywiście prośba o wyrażenie (na przykład, a = b++ + 1;które można zmienić na a = ++b;).

Shahbaz
źródło
4

Czytałem przez większość odpowiedzi tutaj i wielu komentarzach, a ja nie widziałem żadnego odniesienia do jednej instancji, że mogę myśleć, gdzie i++jest bardziej wydajny niż ++i(i być może zaskakująco --i był bardziej skuteczny niż i--). To jest dla kompilatorów C dla DEC PDP-11!

PDP-11 miał instrukcje montażu dotyczące wstępnego zmniejszania rejestru i przyrostowego, ale nie na odwrót. Instrukcje pozwoliły na użycie dowolnego rejestru „ogólnego przeznaczenia” jako wskaźnika stosu. Więc jeśli użyłeś czegoś takiego *(i++), można go skompilować w pojedynczą instrukcję montażu, a *(++i)nie mógł.

Jest to oczywiście bardzo ezoteryczny przykład, ale zapewnia on wyjątek, w którym post-inkrementacja jest bardziej wydajna (lub powinienem powiedzieć , że tak było , ponieważ w dzisiejszych czasach nie ma dużego zapotrzebowania na kod C w PDP-11).

daShier
źródło
2
Bardzo ezoteryczny, ale bardzo interesujący!
Mark Harrison
@daShier. +1, chociaż się nie zgadzam, to nie jest tak ezoteryczne, a przynajmniej nie powinno być. C został opracowany wspólnie z Unixem w AT&T Bell Labs na początku lat 70., kiedy PDP-11 był docelowym procesorem. W kodzie źródłowym uniksowym z tego przyrostu postów „i ++” jest bardziej rozpowszechniony, częściowo dlatego, że programiści wiedzieli, kiedy wartość jest przypisana „j = i ++” lub używana jako indeks „a [i ++] = n”, kod byłby nieco szybszy (i mniejszy). Wygląda na to, że przyzwyczaili się używać przyrostu, chyba że wymagane było wcześniej. Inni nauczyli się czytając ich kod, a także nauczyli się tego nawyku.
jimhark
68000 ma tę samą funkcję, post-inkrementacja i dekrementacja wstępna są obsługiwane sprzętowo (jako tryby adresowania), ale nie na odwrót. Zespół Motoroli został zainspirowany DEC PDP-11.
jimhark
2
@jimhark, tak, jestem jednym z tych programistów PDP-11, którzy przeszli na 68000 i nadal używają --ii i++.
daShier
2

Zawsze wolę wstępne zwiększenie, jednak ...

Chciałem zauważyć, że nawet w przypadku wywołania funkcji operator ++, kompilator będzie w stanie zoptymalizować tymczasowe, jeśli funkcja zostanie wstawiona. Ponieważ operator ++ jest zwykle krótki i często implementowany w nagłówku, prawdopodobnie zostanie wstawiony.

Tak więc, dla celów praktycznych, prawdopodobnie nie ma dużej różnicy między wydajnością tych dwóch form. Zawsze jednak wolę wstępne zwiększenie, ponieważ wydaje się, że lepiej jest bezpośrednio wyrazić to, co próbuję powiedzieć, niż polegać na optymalizatorze, aby to ustalić.

Ponadto, mniej prawdopodobne, że optmizer będzie mniej wymagał, oznacza, że ​​kompilator działa szybciej.


źródło
Twój post jest specyficzny dla C ++, podczas gdy pytanie dotyczy C. W każdym razie twoja odpowiedź jest nieprawidłowa: dla niestandardowych operatorów post-inkrementacyjnych kompilator zasadniczo nie będzie w stanie wygenerować tak wydajnego kodu.
Konrad Rudolph
Stwierdza „jeśli funkcja zostanie wstawiona”, i to sprawia, że ​​jego rozumowanie jest prawidłowe.
Blaisorblade,
Ponieważ C nie zapewnia przeciążenia operatora, pytanie przed vs. jest w dużej mierze nieciekawe. Kompilator może zoptymalizować nieużywaną temperaturę przy użyciu tej samej logiki, która jest stosowana do każdej innej nieużywanej, pierwotnie obliczonej wartości. Zobacz wybraną odpowiedź dla próbki.
0

Moje C jest trochę zardzewiałe, więc z góry przepraszam. Speedwise rozumiem wyniki. Ale jestem zdezorientowany co do tego, jak oba pliki wyszły do ​​tego samego skrótu MD5. Może pętla for działa tak samo, ale czy poniższe 2 wiersze kodu nie wygenerowałyby innego zestawu?

myArray[i++] = "hello";

vs

myArray[++i] = "hello";

Pierwszy zapisuje wartość do tablicy, a następnie zwiększa i. Drugie przyrosty zapisuję następnie w tablicy. Nie jestem ekspertem od montażu, ale po prostu nie widzę, jak te same pliki wykonywalne byłyby generowane przez te 2 różne wiersze kodu.

Tylko moje dwa centy.

Jason Z
źródło
1
@Jason Z Optymalizacja kompilatora następuje przed wygenerowaniem zestawu, zobaczyłby, że zmienna i nie jest używana nigdzie indziej w tej samej linii, więc utrzymanie jej wartości byłoby marnotrawstwem, prawdopodobnie skutecznie przestawia ją na i ++. Ale to tylko przypuszczenie. Nie mogę się doczekać, aż jeden z moich wykładowców spróbuje powiedzieć, że jest szybszy i zostanę facetem, który koryguje teorię praktycznymi dowodami. Już prawie czuję animozję ^ _ ^
Tarks
3
„Moje C jest trochę zardzewiałe, więc z góry przepraszam”. Nie ma nic złego w twoim C, ale nie przeczytałeś w całości oryginalnego pytania: „Czy istnieje różnica w wydajności między i ++ i ++ i, jeśli wynikowa wartość nie jest używana? ” Zwróć uwagę kursywą. W swoim przykładzie, „wynik” z I ++ / ++ i jest używany, ale w idiomatyczne dla pętli „wynik” wstępnego / operatora postincrement nie służy więc kompilator może robić to, co lubi.
Roddy
2
w twoim przykładzie kod byłby inny, ponieważ używasz wartości. przykładem, w którym były takie same, było użycie ++ tylko dla przyrostu, a nie użycie wartości zwróconej przez jedno z nich.
John Gardner,
1
Poprawka: „kompilator zauważyłby, że wartość zwracana i ++ nie jest używana, więc zmieniłby ją na ++ i”. To, co napisałeś, jest błędne również dlatego, że nie możesz mieć i razem z jednym z i ++, i ++ w tej samej linii (instrukcji), wynik jest niezdefiniowany.
Blaisorblade,
1
Zmieniających foo[i++]się foo[++i]bez zmieniania czegokolwiek innego będzie oczywiście zmienić semantykę program, ale w przypadku niektórych procesorów przy użyciu kompilatora bez podnoszenia pętli logiki optymalizacji, zwiększając pi qraz, a następnie uruchomić pętlę, która wykonuje np *(p++)=*(q++);byłoby szybsze niż za pomocą pętli, która wykonuje *(++pp)=*(++q);. W przypadku bardzo ciasnych pętli na niektórych procesorach różnica prędkości może być znacząca (ponad 10%), ale jest to prawdopodobnie jedyny przypadek w C, w którym przyrostowo jest znacznie szybszy niż przyrost wstępny.
supercat