Na stronie kodzimy.net znalazłem bardzo fajny artykuł o szybkości działania i++ i ++i. Wniosek z artykułu brzmi: nie ma różnicy dla podstawowych typów w nowoczesnych kompilatorach. Jako, że temat jest mi bliski, to dorzucę dwie ciekawostki :)

1. i++ oraz ++i robi różnicę w jednym (ważnym) miejscu - jeśli "i" jest obiektem klasy która ma przeciążone operatory Klasa++ i ++Klasa. Z uwagi na to jak operator postfixowy Klasa++ działa, konieczne w nim jest (w zasadzie zawsze) wykonanie kopii obiektu na którym się operuje, tak żeby można tą kopie zwrócić. Np.:

class C {
 public:

   int v;
   C():v(0){}

   C& operator++() { /* ++C */
     v += 1; return *this;
   }

   C operator++(int) { /* C++ */
     C n; n.v = v; v += 1; return n;
   }
};

W powyższym wypadku jeśli użyjemy i++ zamiast ++i, to faktycznie będzie wolniejsze, ponieważ i++ i ++i to są de facto różne funkcje, z których i++ będzie wolniejsza z uwagi na wykonywanie kopii obiektu*.
* - pewnie w powyższym przypadku można by wskazać, że kompilator może podczas optymalizacji wygenerować alternatywną metodę C operator++(int) która nie generuje w ogóle kopii obiektu i nic nie zwraca (w końcu w for'owym zapisie i tak nie patrzy się na zwracany obiekt przy inkrementacji), natomiast trzeba zauważyć, że równie dobrze konstruktor C mógł zrobić jakieś "puts" czy inny "malloc", i wtedy taka optymalizacja nie jest już możliwa.

Dodam, że powyższe jest ważne jak operuje się na iteratorach, np list<>::iterator, etc. Lub oczywiście na klasach reprezentujących jakiś matematyczny obiekt na którym można wykonywać działania arytmetyczne (np. wektor 3D, bignum, etc).

2. Będąc jeszcze na studiach jeden z profesorów stwierdził, że i += 1 jest szybsze niż i = i + 1. Będąc natrętnym studentem oczywiście po wykładzie podszedłem i zapytałem "how come", ponieważ jest to niezgodne z tym co faktycznie nowoczesne kompilatory generują. Odpowiedź jaką uzyskałem była ciekawa - mianowicie profesor powiedział, że swoją wypowiedź bazował na doświadczeniach z pewnym kompilatorem, który jeśli widział wyrażenie bardziej skomplikowane niż "i += 1", to tworzył wywołanie do specjalnej funkcji która runtime robiła obliczenia matematyczne. Inaczej: kompilator nie kompilował wyrażeń matematycznych do asma, zamiast tego zapisywał je w postaci pośredniej i wywoływał funkcję która z danych + wyrażenia zwracała wynik. Brzmi to bardzo wolno (choć kod wynikowy może być mniejszy dla dużych programów robiących dużo obliczeń).

By the way...
There are more blog posts you might like on my company's blog: https://hexarcana.ch/b/


Nie pamiętam już czy dostałem odpowiedź na pytanie "jaki to kompilator", lub czy w ogolę zadałem takie pytanie (chyba nie), natomiast abstrahując nawet od tego jaki to był kompilator, czy faktycznie taki był (nie widzę powodu dla którego miałoby takiego kompilatora nie być - nie takie hacki już widziałem) i kiedy to było, to odpowiedź mi się bardzo podobała z jednego względu - było to całkiem inne spojrzenie na temat :)
I o ile nie sądzę, żeby jakiś kompilator obecnie coś takiego robił, to należy pamiętać, że istnieją również interpretery C/C++, które bym bardziej podejrzewał o takie zachowanie.

I tyle,

P.S. ponieważ jest to bikeshed, to zachęcam do komentowania ;)

Comments:

2013-02-18 10:07:30 = KrzaQ
{
Świetny art., nawet bym nie pomyślał, że side-effect w konstruktorze może być tak zgubienny w skutkach (chociaż w sumie teraz wydaje się to oczywiste).

Ale przy stl-owych iteratorach nie ma większej różnicy, którego zapisu użyjemy (przynajmniej w wiodących kompilatorach)
}
2013-02-18 16:08:46 = takwlasnie
{
No i popatrz, bardzo dobry przykład, ale zawiera inne złe przyzwyczajenie z języków typu Java, PHP.

Rozumiem, że to przykład i dlatego wszystko jest public, ale dla jasności zamiast:

C n; n.v = v;

Lepiej byłoby napisać:

C n(*this);

W C++ konstruktor domyślny i kopiujący oraz operator przypisania są automatycznie tworzone dla każdej klasy i należałoby z nich korzystać. Należy je przeładować tylko wtedy, kiedy dynamicznie alokujemy pamięć.

Choć w tym konkretnym przypadku zapis tworzenia nowego obiektu nie wpłynie na czas wykonywania programu, ponieważ klasa jest bardzo prosta, należy pamiętać, że w ten sposób wywołuje się konstruktor domyślny i operator przypisania (kiedy to samo można zrobić za pomocą konstruktora kopiującego), czyli wywołuje się jedną funkcję zupełnie niepotrzebnie.
}
2013-02-18 18:52:13 = aau
{
Do samego wpisu nie mam raczej nic do dodania, więc tak tylko odnośnie PS napiszę.
Nie sądzisz że raczej najpierw trzeba by było zwiększyć liczbę czytelników wracając do nieco większej częstotliwości wpisów, zanim takie zabiegi będą miały sens? :)
}
2013-02-18 20:06:55 = inquis1t0r
{
Ad. 2
Może warto byłoby sprawdzić jak wyglądał kod generowany przed kompilatory z czasów...powiedzmy z przed Ansi C, albo innym, możliwie jak najstarszym ?

Zresztą zawsze miałem wrażenie, że np. wymaganie od studentów używania i+=i, zamiast i=i+1, wynika z przyjętego sposobu zapisu, spowodowanego zwyczajnym lenistwem (;p) i przypadkowym przyjęciem tej formy zapisu jako "dobrej praktyki programistycznej".

Dodatkowo w książkach ciągle są powtarzane nieco mgliste twierdzenia, że "Wyr1 op= Wyr2 różni się od
wyrażenia Wyr1 = Wyr1 op (Wyr2) jedynie tym, że Wyr1 jest obliczane jedynie raz."
Co chyba raczej było ostrzeżeniem, w razie stosowania kompilatora, który został źle pod tym kontem zoptymalizowany. Przynajmniej taką teorią znalazłem tutaj:http://in.answers.yahoo.com/question/index?qid=20111017212348AALIAYh
i w tej dyskusji również wynikowe kody asm pokazują, że współczesne kompilatory sprowadzają wszystko do jednego.

A co do P.S'a
Możliwe, że taką zachętą odstraszysz od komentowania, bo nikt nie będzie chciał otwarcie się przyznać, że ich kompetencje wystarczają jedynie na napisanie komentarza do wpisu wymagającego "dolnej granicy" kompetencji.
I mam nadzieję, że jednak uda mi się uniknąć posądzenia o niedoinformowanie, bo teraz to już naprawdę musiałoby mi być wstyd :D
}
2013-02-18 20:26:26 = Ezo
{
Ciekawy post. Zawsze myślałem że ++i jest szybsze od i++. Teraz sprawdziłem, i nawet w dość skomplikowanych wyrażeniach nie ma różnicy.
}
2013-02-19 07:33:57 = Gynvael Coldwind
{
@KrzaQ
Nie ma *większej* różnicy, czy *żadnej* różnicy? :)

@takwlasnie
Masz rację oczywiście. Natomiast takiego zapisu użyłem celowo, żeby wskazać nadmiarowe kopiowanie danych.

@aau
P.S. był z przymrużeniem oka :)
Natomiast ogólnie masz rację - w tym roku wrzuciłem dopiero 4 posty, co daje jakieś pos na 2.5 tygodnia. Trudno mi coś obiecać jeśli chodzi o częstotliwość postowania z uwagi na inne projekty, ale postaram się coś częściej wrzucać.


}
2013-02-19 08:54:36 = KrzaQ
{
@Gynvael
Dla gcc 4.7 i 4.8 przy -O3 dla std::vector<int> i std::list<int> nie ma żadnej. (testowałem tutaj: http://gcc.godbolt.org/ )

Słowo "większej" wydawało mi się bezpieczniejsze, bo optymalizacja to domena kompilatora, a nie języka jako takiego, ponadto nie jest dokonywana domyślnie. Ale w wiodących kompilatorach myślę, że śmiało można założyć, że nie ma żadnej ;)
}
2013-02-19 11:06:42 = Gynvael Coldwind
{
@inquis1t0r
Thx za input i linka. Rzuce na to okiem po pracy na wskazaną dyskusję :)

Btw, wczoraj przegladajac standard rzucilo mi sie w oczy stwierdzenie, ze wyrazenia "++i" oraz "i+=1" sa tożsame, z wyłączeniem sytuacji gdzie "i" jest obiektem. Natomiast nie doczytałem czy jest to zalecenie dla kompilatorów, czy po prostu stwierdzenie, że side-effecty/wynik ewaluacji jest identyczny.

Ad. P.S. Jak pisałem wyżej, wskazanie na post o bikeshed było z przymrużeniem oka ;>
Zresztą, nie sądzę, żeby napisanie komentarza pod "bikeshedowym" tematem było równoważne stwierdzeniu, że dana osoba nie napisze komentarza pod innymi, mniej bikeshedowymi, tematami. A przynajmniej ja tak na to nie patrzę ;>

@Ezo
Taak, to często powtarzany mit (z ziarenkiem prawdy).
Btw, jak sprawdzałeś szybkość?


I jeszcze ogólnie: jak wiecie komentarze są obecnie moderowane (a konkretniej - wycinam wszystko co wygląda jak SEO/SPAM) przez co są delay'e w ich pojawianiu się. Fixnąłem rano jeden bug w kodzie, więc komentarze bez żadnych linków powinny się zatwierdzać od razu (hehe newbie's bug: if(a=b) zamiast if(a===b) ;p). Natomiast chwilowo jestem w stanie zatwierdzać pozostałe komentarze jedynie jak jestem w domu - więc przepraszam za delay i postaram się "coś z tym zrobić" żeby to przyspieszyć ;)
}
2013-02-19 11:33:24 = KrzaQ
{
WP powinien pozwalać na moderację tylko pierwszego komentarza danego użytkownika, po ciasteczku. Jeśli nie siedzisz na WP, to dodanie takiej funkcjonalności i tak nie powinno być specjalnie trudne.
}
2013-02-19 23:18:54 = Gynvael Coldwind
{
@KrzaQ
Ad testy - thx za update i za linka. Nie znałem tej stronki, a wydaje się bardzo przydatna :)
Ad moderacja - mam jakiś bardziej automatyczny whitelisting na TODO liście, a póki co ręcznie whitelistuje (jeśli pamiętam ;>).
}
2013-02-23 00:10:34 = Ezo
{
@Gynvael Coldwind

Sprawdziłem wygenerowany kod (kompilator dołączony do Visual Studio 2012). Wyglądał praktycznie identycznie - z tego co pamiętam różnica była tylko przy przypisywaniu do innej zmiennej - przy preinkrementacji inkrementacja była wykonywana przed przypisaniem, a przy postinkrementacji po przypisaniu. Ale to akurat jest oczywiste :P. Wątpię by zmiana położenia jednej instrukcji zmieniała cokolwiek w szybkości.

Przy samodzielnej(bez przypisywania post/pre inkrementowanej zmiennej) postinkrementacji i preinkrementacji nie było chyba żadnej różnicy.
}
2013-03-04 15:10:44 = machops
{
Ciekawy art, i o ile dobrze kojaże, to różne kompilatory, różnie interpretują pre inkrementacje (szczególnie w pętli for). Czasami zdarza się ze kompilator zinterpretuje jako post inkrementacje.
}
2013-07-17 17:28:51 = mina86
{
Dodam tylko, iż kompilatory C i C++ działają na zasadzie “observable behaviour”, czyli nie muszą robić wszystkiego co im kod nakazuje o ile są w stanie udowodnić, że nie zmieniają zachowania. Na prostym przykładzie, oznacza to, że w kodzie typu:

unsigned i;
for (i = 0; ++i;);
printf("Reached %u. Seems pointless...", i);

mogą zwyczajnie zamienić pętlę na “i = 0;”.

Równocześnie STL to szablony. Szablony są inline. A jak inline, to kompilator ma dostęp do implementacji[*] i przy odrobinie szczęścia może być w stanie stwierdzić, że kopiowanie wykonane przez preinkrementację nie zmienia zachowania kodu, zatem jest zbędne i można je wywalić.

Niemniej, to wszystko zależy i żeby mieć pewność zawsze lepiej postawić plusiki prze nazwą zmiennej.

[*] Choć szablon może być zaledwie wraperem do funkcji do kodu których kompilator dostępu nie ma.
}

Add a comment:

Nick:
URL (optional):
Math captcha: 10 ∗ 4 + 6 =