Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

PS. do tematu "Przeczytany, zamknięty Czy da się bez użycia warunków if"

Ostatnio zmodyfikowano 2011-01-01 14:42
Autor Wiadomość
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
P-25667
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
P-25668
ison
» 2010-12-27 16:50:37
b++ szybsze niż b+1
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

a & b szybsze niż a==b
to nie to samo
P-25669
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ę.
P-25670
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:

kod int unsigned int float
a / 2
; eax: a
cdq ; edx[31..0] = eax[31]
sub eax, edx ; eax -= edx
sar eax, 1 ; eax >> 1 (arithmetic)
; eax: a
shr eax, 1 ; eax >> 1
; xmm0: a
; __real@3f000000 dd 0x3f000000
mulss xmm0, dword [__real@3f000000] ; xmm0 *= 0.5
a * 0.5
; eax: a
; __real@3fe0000000000000 dq 0x3fe0000000000000
xorps xmm0, xmm0 ; xmm0[127..0] = 0
cvtsi2sd xmm0, eax ; xmm0[63..0] = double(eax)
mulsd xmm0, qword [__real@3fe0000000000000] ; xmm0 *= 0.5
cvtsd2si eax, xmm0 ; eax = int(xmm0[63..0])
kod jest dosyć długi, w każdym razie sprowadza się do konwersji do double (która to zajmuje najwięcej), mnożenia i konwersji z powrotem do unsigned int
; xmm0: a
; __real@3f000000 dd 0x3f000000
mulss xmm0, dword [__real@3f000000] ; xmm0 *= 0.5

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.
P-25671
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?
P-25672
jsc
Temat założony przez niniejszego użytkownika
» 2010-12-27 17:16:40
a << 1
P-25676
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? ;>
P-25677
1 « 2 » 3 4 5 6 7 8
Poprzednia strona Strona 2 z 8 Następna strona