pekfos |
» 2010-12-27 16:16:39 proste sztuczki? np to: for( int a = 0; a < 100000; a++ ) for( int b = 0; b < 10; ++b ) { int x = a * b / 12; } jest mniej efektywne od tego: for( int a = 0, b, x; a < 100000; a++ ) for( b = 0; b < 10; ++b ) { x = a * b / 12; } dlatego że deklarowanie zmiennej w pętli oznacza ze zmienna zostanie utworzona i zniszczona n razy. co oczywiście trochę trwa. czasem nawyki z C się przydają. deklaruj zmienne tymczasowe używanie w pętlach wcześniej a kod będzie szybszy. a dokładniej szybsze o 100000 tworzenia i niszczenia lokalnego b i 1000000 tworzenia i niszczenia lokalnego x |
|
jsc Temat założony przez niniejszego użytkownika |
» 2010-12-27 16:45:25 Jak wy tak piszecie to otworzyło mi się w głowie i: b++ szybsze niż b+1 a & b szybsze niż a==b |
|
ison |
» 2010-12-27 16:50:37 nie możesz porównać tych dwóch rzeczy... jak już to b++; oraz b+=1; b++ to również przypisanie jeśli w danym momencie nie mają znaczenia priorytety operatorów to staraj się używać preinkrementacji (++b) - nie tworzysz wtedy obiektu tymczasowego to nie to samo |
|
jsc Temat założony przez niniejszego użytkownika |
» 2010-12-27 16:59:33 Czym różni się iloczyn logiczny od porównania. W tablicy prawdy dla bitów wychodzi to samo.
A błąd logiczny.
Masz rację. |
|
Elaine |
» 2010-12-27 17:03:48 @ison: no jasne, kompilatory nie potrafią przeprowadzać strength reduction...
Nie, zaraz, potrafią. W dodatku lepiej, niż większość ludzi.
a * 0.5 wcale nie będzie szybsze, niż a / 2 , co najwyżej wyjdzie na to samo, a i to tylko, jeśli a jest typu zmiennoprzecinkowego:
Co widać? Przede wszystkim, a / 2 pozwoliło kompilatorowi na wygenerowanie porządnego kodu, bez żadnego dzielenia. Tylko... dlaczego w przypadku int nie jest to po prostu sar eax, 1 ? Z prostego powodu, dzielenie zaokrągla do zera, przesunięcie bitowe zaokrągla do minus nieskończoności - właśnie dlatego są dodatkowe (tanie) instrukcje.
a * 0.5 natomiast wymusiło na kompilatorze wygenerowanie konwersji do liczby zmiennoprzecinkowej, mnożenia zmiennoprzecinkowego i konwersji z powrotem do liczby całkowitej - co jest niepotrzebnie skomplikowane (a więc i wolne), skoro można to zrobić bez konwersji.
Oczywiście kompilator da sobie radę również w przypadku, gdy zamiast dwójki będzie inna jej potęga. Ba - nawet w przypadku, gdy dzielnik nie będzie potęgą dwójki, kompilator zwykle i tak nie będzie używał instrukcji do dzielenia, zamiast tego pomnoży przez stałą (plus ewentualnie kilka przesunięć bitowych, dodawań i odejmowań - czyli tanich operacji) w przypadku liczb całkowitych, a w przypadku liczb zmiennoprzecinkowych pomnoży przez odwrotność.
Oczywiście te kody wyżej są x86-specific, ale na innych architekturach jest podobnie.
@pekfos: to jest prawdą tylko dla typów, których konstrukcja/destrukcja faktycznie coś zajmuje (czyli tych, które mają konstruktor/destruktor, który wykonuje jakąś konkretniejszą robotę). Dla typów POD wyjdzie dokładnie na to samo - miejsce w ramce stosu i tak jest alokowane tylko raz, przy wejściu do funkcji, kosztów konstrukcji i destrukcji nie ma wcale. |
|
ison |
» 2010-12-27 17:08:08 @Iname ok, a co z przesunięciem bitowym zamiast dzielenia/mnożenia przez potęgi dwójki? W ogóle tego nie stosować bo kompilatory i tak sobie to zamienią jak będą chciały?
Co jest w takim razie lepszym rozwiązaniem i*2 czy i/0.5? |
|
jsc Temat założony przez niniejszego użytkownika |
» 2010-12-27 17:16:40 a << 1 |
|
Elaine |
» 2010-12-27 17:18:52 Jeśli chodzi o dzielenie to masz wyżej, dla innych potęg dwójki zmienią się tylko stałe i sekwencja dla inta trochę się zmieni (dojdzie jeden and, zamiast odejmowania będzie dodawanie - wciąż jednak to będą same tanie instrukcje, tylko cztery zamiast trzech - lepiej się tego zrobić nie da, bez naruszania zaokrąglania do zera).
Jeśli chodzi o mnożenie:
Dla liczb całkowitych a * X , gdzie X jest potęgą dwójki, to będzie jedno przesunięcie w lewo (wyjątek: jeśli mnożymy przez 2, wtedy to będzie dodanie wartości do samej siebie), teraz już bez żadnych cyrków z bitem znaku. Podobnie też jak wyżej, jeśli X nie będzie potęgą dwójki, to kod niekoniecznie też będzie zawierał mnożenie - kompilator wykorzysta przesunięcia bitowe, dodawanie, odejmowanie i instrukcję lea (oczywiście nie zawsze - mnożenie jest stosunkowo szybkie, czasami ciąg wspomnianych instrukcji będzie wolniejszy niż imul , wtedy po prostu kompilator zrobi ten imul ). Dla liczb zmiennoprzecinkowych to z kolei będzie pojedyncza instrukcja mnożenia.
a /( reciprocal of X ) z kolei będzie podobnym cyrkiem, jak wyżej a * 0.5 - zbędne konwersje dla liczb całkowitych, jedno mnożenie dla zmiennoprzecinkowych.
Takie mikrooptymalizacje nie mają sensu - jak chcesz dzielić/mnożyć, to użyj w kodzie dzielenia/mnożenia, kompilator już zadba o to, by wygenerowany kod robił to w jak najszybszy sposób.
Swoją drogą - a << 1 kompiluje się właśnie do pojedynczego dodawania. Nie spodziewaliście się tego, nie? ;> |
|
1 « 2 » 3 4 5 6 7 8 |