Która z poniższych technik jest najlepszą opcją do podzielenia liczby całkowitej przez 2 i dlaczego?
Technika 1:
x = x >> 1;
Technika 2:
x = x / 2;
Oto x
liczba całkowita.
c++
c
optimization
division
micro-optimization
Abhineet
źródło
źródło
x
, żadna z nich nie jest odpowiednia w ten sposób: powinna być albo,x >>= 1
albox /= 2
, w zależności od tego, co zamierzasz wyrazić za pomocą operacji. Nie dlatego, że jest szybszy (każdy nowoczesny kompilator i tak skompiluje wszystkie równoważne warianty do identycznego, szybkiego zestawu), ale dlatego, że jest mniej mylący.x = x >>> 1
. Należy również pamiętać, że w zależności od platformy i kompilatora może być całkiem uzasadnione ręczne optymalizowanie podziałów i mnożenia za pomocą przesunięć. - Myśląc na przykład o mikrokontrolerach, bez bezpośredniej obsługi ALU dla mnożenia.x /= 2
ponieważx >>= 1
zbyt przypomina monadic bind;)x = x / 2
zamiastx /= 2
. Subiektywne preferencje może :)⬜=
kombinacje, należy ich używać, gdy tylko jest to możliwe. Usuwa hałas i kładzie nacisk na fakt, żex
jest modyfikowany , podczas gdy główny=
operator raczej sugeruje, że przyjmuje zupełnie nową wartość, niezależnie od starej. - Zawsze unikając operatorów połączonych (tak, to czytelny więc ktoś, kto zna tylko operatorów matematycznych) może mieć swój punkt jak dobrze, ale wtedy trzeba by zrezygnować z niezwykle przydatna++
,--
,+=
, too.Odpowiedzi:
Użyj operacji, która najlepiej opisuje to, co próbujesz zrobić.
Pamiętaj, że nie są dokładnie równoważne. Mogą dawać różne wyniki dla liczb całkowitych ujemnych. Na przykład:
(ideone)
źródło
%
i/
muszą być spójne zarówno dla argumentów dodatnich, jak i ujemnych, aby było(a/b)*b+(a%b)==a
to prawdą niezależnie od znakówa
ib
. Zazwyczaj autor dokonuje wyborów zapewniających najlepszą możliwą wydajność procesora.Czy ten pierwszy wygląda na dzielenie? Nie. Jeśli chcesz podzielić, użyj
x / 2
. Kompilator może go zoptymalizować pod kątem przesunięcia bitów, jeśli to możliwe (nazywa się to redukcją siły), co czyni go bezużyteczną mikrooptymalizacją, jeśli robisz to sam.źródło
Kupowanie: istnieje wiele powodów, dla których warto skorzystać z
x = x / 2;
Oto kilka:wyraźniej wyraża twoją intencję (zakładając, że nie masz do czynienia z bitami rejestru bitowymi lub czymś podobnym)
kompilator i tak zredukuje to do zmiany
nawet jeśli kompilator nie zmniejszył go i wybrał wolniejszą operację niż zmiana, prawdopodobieństwo, że wpłynie to na wydajność twojego programu w wymierny sposób, samo w sobie jest znikomo małe (a jeśli ma to wymierny wpływ, to masz rzeczywistą powód, aby użyć zmiany)
jeśli podział będzie częścią większego wyrażenia, bardziej prawdopodobne jest uzyskanie pierwszeństwa, jeśli użyjesz operatora dzielenia:
podpisana arytmetyka może komplikować sprawy jeszcze bardziej niż wspomniany wyżej problem pierwszeństwa
Powtórzmy - kompilator i tak zrobi to za ciebie. W rzeczywistości przekształci dzielenie przez stałą na serię przesunięć, sum i mnożeń dla wszystkich rodzajów liczb, nie tylko potęg dwóch. Zobacz to pytanie, aby uzyskać dostęp do dodatkowych informacji na ten temat.
Krótko mówiąc, nic nie kupujesz, kodując zmianę, gdy naprawdę chcesz pomnożyć lub podzielić, z wyjątkiem być może zwiększonej możliwości wprowadzenia błędu. Minęło całe życie, odkąd kompilatory nie były wystarczająco inteligentne, aby w razie potrzeby zoptymalizować tego rodzaju zmiany.
źródło
a/b/c*d
(gdziea..d
oznaczono zmienne numeryczne) zamiast o wiele bardziej czytelnego(a*d)/(b*c)
.a*d
lubb*c
przepełnienia, mniej czytelna forma nie jest równoważna i ma oczywistą zaletę. PS Zgadzam się, że nawiasy są twoim najlepszym przyjacielem.a/b/c*d
kod R - w kontekście, w którym przepełnienie oznaczałoby, że coś było poważnie nie tak z danymi - a nie, powiedzmy, w krytycznym dla wydajności bloku kodu C).x=x/2;
jest tylko „wyraźniejszy” niżx>>=1
wtedyx
, gdy nigdy nie będzie nieparzystą liczbą ujemną lub nikt nie przejmuje się błędami pojedynczymi. W przeciwnym raziex=x/2;
ix>>=1;
mają różne znaczenia. Jeśli potrzebna jest wartość obliczona przezx>>=1
, uważałbym to za jaśniejsze niżx = (x & ~1)/2
lubx = (x < 0) ? (x-1)/2 : x/2
, lub jakikolwiek inny sposób, który mogę pomyśleć o zastosowaniu podziału na dwa. Podobnie, jeśli potrzebna jest obliczona wartośćx/=2
, to jest wyraźniejsze niż((x + ((unsigned)x>>31)>>1)
.Zależy co masz na myśli najlepiej .
Jeśli chcesz, aby twoi koledzy cię nienawidzili lub utrudniali czytanie kodu, zdecydowanie wybrałbym pierwszą opcję.
Jeśli chcesz podzielić liczbę przez 2, przejdź do drugiej.
Te dwa nie są równoważne, nie zachowują się tak samo, jeśli liczba jest ujemna lub wewnątrz większych wyrażeń - przesunięcie bitowe ma niższy priorytet niż
+
lub-
, podział ma wyższy priorytet.Powinieneś napisać kod, aby wyrazić jego zamiary. Jeśli zależy Ci na wydajności, nie martw się, optymalizator wykonuje dobrą robotę przy tego rodzaju mikrooptymalizacjach.
źródło
Wystarczy użyć divide (
/
), zakładając, że jest to bardziej przejrzyste. Kompilator odpowiednio zoptymalizuje.źródło
ASSUME(x >= 0); x /= 2;
nadx >>= 1;
, ale to jest nadal ważnym punktem wychowywać.Zgadzam się z innymi odpowiedziami, które należy faworyzować
x / 2
ponieważ jego zamiary są jaśniejsze, a kompilator powinien go zoptymalizować.Jednak kolejny powód, dla preferujących
x / 2
nax >> 1
to, że zachowanie>>
jest zależna od implementacji jeślix
jest podpisanaint
i jest ujemna.Z sekcji 6.5.7 punkt 5 normy ISO C99:
źródło
x>>scalepower
liczb ujemnych, będzie dokładnie tym, czego potrzeba, dzieląc wartość przez potęgę dwóch do celów takich jak renderowanie ekranu, podczas gdy użyciex/scalefactor
będzie nieprawidłowe, chyba że zastosuje się poprawki do wartości ujemnych.x / 2
jest bardziej przejrzysty ix >> 1
nie jest dużo szybszy (według mikro-testu porównawczego, około 30% szybszy dla JVM Java). Jak zauważyli inni, w przypadku liczb ujemnych zaokrąglenie jest nieco inne, więc należy wziąć to pod uwagę, gdy chcesz przetwarzać liczby ujemne. Niektóre kompilatory mogą się automatycznie konwertowaćx / 2
nax >> 1
, jeśli wiedzą, że liczba ta nie może być ujemna (nawet, że nie mogłem to sprawdzić).Nawet
x / 2
nie może używać instrukcji procesora (powolnego) podziału, ponieważ niektóre skróty są możliwe , ale wciąż jest wolniejsze niżx >> 1
.(Jest to C / C ++ pytanie, inne języki programowania mają więcej operatorów. Dla Java istnieje również bez znaku prawy shift
x >>> 1
, który jest znowu inny. To pozwala prawidłowo obliczyć średnią (średnia) wartość dwóch wartości, tak że(a + b) >>> 1
woli zwraca wartość średnią nawet dla bardzo dużych wartościa
ib
. Jest to wymagane na przykład do wyszukiwania binarnego, jeśli indeksy tablic mogą być bardzo duże. Wystąpił błąd w wielu wersjach wyszukiwania binarnego , ponieważ używano ich(a + b) / 2
do obliczania średniej. nie działa poprawnie.(a + b) >>> 1
Zamiast tego należy użyć poprawnego rozwiązania .)źródło
x/2
nax>>1
przypadki, w którychx
mogą być ujemne. Jeśli pragniemy wartości, którax>>1
byłaby obliczona, prawie na pewno będzie szybsza niż jakiekolwiek wyrażenie,x/2
które oblicza tę samą wartość.x/2
dox>>1
jeśli zna wartość nie jest negatywna. Spróbuję zaktualizować swoją odpowiedź.div
instrukcji, konwertującx/2
na(x + (x<0?1:0)) >> 1
(gdzie >> jest arytmetyką przesunięcia w prawo, która zmienia bity znakowe). Wymaga to 4 instrukcji: skopiuj wartość, shr (aby uzyskać tylko bit znaku w reg), dodaj, sar. goo.gl/4F8Ms4Knuth powiedział:
Więc proponuję użyć
x /= 2;
W ten sposób kod jest łatwy do zrozumienia, a także myślę, że optymalizacja tej operacji w tej formie nie oznacza dużej różnicy dla procesora.
źródło
(n+8)>>4
działa dobrze. Czy możesz zaoferować jakieś podejście, które jest tak jasne lub tak efektywne bez korzystania z podpisanej prawej zmiany?Spójrz na dane wyjściowe kompilatora, które pomogą Ci podjąć decyzję. Uruchomiłem ten test na x86-64 z
gcc (GCC) 4.2.1 20070719 [FreeBSD]
Zobacz także wyniki kompilatora online w godbolt .
To, co widzisz, to kompilator
sarl
w obu przypadkach korzysta z instrukcji (arytmetyczne przesunięcie w prawo), więc rozpoznaje podobieństwo między tymi dwoma wyrażeniami. Jeśli użyjesz podziału, kompilator musi również dostosować liczby ujemne. W tym celu przesuwa bit znaku do bitu najniższego rzędu i dodaje go do wyniku. Rozwiązuje to problem „jeden po drugim” przy przesuwaniu liczb ujemnych w porównaniu do tego, co zrobiłby podział.Ponieważ przypadek podziału wykonuje dwie zmiany, podczas gdy wyraźny przypadek zmiany robi tylko jedną, możemy teraz wyjaśnić niektóre różnice wydajności mierzone w innych odpowiedziach tutaj.
Kod C z wyjściem zespołu:
W przypadku podziału twój wkład byłby
i to się kompiluje
podobnie do zmiany
z wyjściem:
źródło
d
. Takie partycjonowanie jest przydatne do wielu celów. Nawet jeśli wolisz punkt przerwania gdzieś indziej niż między 0 a -1, dodanie przesunięcia spowoduje jego przesunięcie. Podział liczb całkowitych, który spełnia drugi aksjomat, podzieli linię liczbową na regiony, które są przeważnie wielkościd
, ale jeden z nich ma rozmiar2*d-1
. Niezupełnie „równe” podziały. Czy możesz podać sugestie, kiedy partycja z nieparzystą kulą jest rzeczywiście przydatna?sar
). goo.gl/KRgIkb . Ten post z listy mailingowej ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) potwierdza, że gcc historycznie używa przesunięć arytmetycznych dla podpisanych znaków int , więc jest bardzo mało prawdopodobne, że FreeBSD gcc 4.2.1 użył przesunięcia bez znaku. Zaktualizowałem twój post, aby to naprawić, i na początku akapitu, mówiąc, że oba użyły Shr, kiedy tak naprawdę SAR używają oboje. SHR to sposób, w jaki wyodrębnia bit znaku dla/
sprawy. Zawiera również godbolt link.Tylko dodana notatka -
x * = 0,5 często będzie szybszy w niektórych językach opartych na maszynach wirtualnych - zwłaszcza w ActionScript, ponieważ zmienna nie będzie musiała być sprawdzana pod kątem dzielenia przez 0.
źródło
eval
) Sprawiły, że stało się to za każdym razem. Niezależnie od tego, tak, jest to dość zły test, ponieważ jest to bardzo głupia optymalizacja.Użyj
x = x / 2;
LUBx /= 2;
Ponieważ możliwe jest, że nowy programista będzie nad nim pracował w przyszłości. Dzięki temu łatwiej będzie mu dowiedzieć się, co się dzieje w wierszu kodu. Każdy może nie być świadomy takich optymalizacji.źródło
Mówię w celu programowania konkursów. Zasadniczo mają one bardzo duże dane wejściowe, w których dzielenie przez 2 odbywa się wiele razy i wiadomo, że dane wejściowe są dodatnie lub ujemne.
x >> 1 będzie lepszy niż x / 2. Sprawdziłem na ideone.com, uruchamiając program, w którym miało miejsce ponad 10 ^ 10 podziałów na 2 operacje. x / 2 zajęło prawie 5,5 s, podczas gdy x >> 1 zajęło prawie 2,6 s dla tego samego programu.
źródło
x/2
dox>>1
. W przypadku wartościx>>1
ze znakiem prawie wszystkie implementacje mają znaczenie, które jest równoważne,x/2
ale może być obliczone szybciej, gdyx
jest dodatnie, i użytecznie różni się odx/2
kiedyx
jest ujemne.Powiedziałbym, że jest kilka rzeczy do rozważenia.
Przesunięcie bitów powinno być szybsze, ponieważ tak naprawdę nie jest potrzebne żadne specjalne obliczenie do przesunięcia bitów, jednak, jak wskazano, istnieją potencjalne problemy z liczbami ujemnymi. Jeśli masz pewność, że masz liczby dodatnie i szukasz prędkości, poleciłbym bitshift.
Operator podziału jest bardzo łatwy do odczytania przez ludzi. Więc jeśli szukasz czytelności kodu, możesz to wykorzystać. Zwróć uwagę, że pole optymalizacji kompilatora przeszło długą drogę, więc ułatwienie odczytu i zrozumienia kodu jest dobrą praktyką.
Jeśli zależy Ci na czystej wydajności, zaleciłbym utworzenie testów, które mogłyby wykonać operacje miliony razy. Próbkuj wykonanie kilka razy (wielkość twojej próby), aby ustalić, który jest statystycznie najlepszy z twoim OS / Sprzętem / Kompilatorem / Kodem.
źródło
>>
działa, a co/
nie.Jeśli chodzi o procesor, operacje przesunięcia bitów są szybsze niż operacje dzielenia. Jednak kompilator wie o tym i odpowiednio zoptymalizuje w zakresie, w jakim jest to możliwe, abyś mógł kodować w sposób, który jest najbardziej sensowny, i odpoczywać, wiedząc, że Twój kod działa skutecznie. Pamiętaj jednak, że
unsigned int
można (w niektórych przypadkach) zoptymalizować lepiej niż zint
wcześniej wymienionych powodów. Jeśli nie potrzebujesz arytmatyki ze znakiem, nie dołączaj bitu znaku.źródło
x = x / 2; jest odpowiednim kodem do użycia ... ale operacja zależy od twojego programu, w jaki sposób wynik chcesz wygenerować.
źródło
Wyjaśnij swoje intencje ... na przykład, jeśli chcesz podzielić, użyj x / 2 i pozwól kompilatorowi zoptymalizować go, aby przesunął operatora (lub cokolwiek innego).
Dzisiejsze procesory nie pozwolą, aby te optymalizacje miały jakikolwiek wpływ na wydajność twoich programów.
źródło
Odpowiedź będzie zależeć od środowiska, w którym pracujesz.
x /= 2
wx >>= 1
obecność symbolu podziału podniesie brwi więcej, niż w tym środowisku używając Shift, aby dokonać podziału.x >>= 1
komentarz wyjaśniający jego uzasadnienie jest prawdopodobnie najlepszy ze względu na przejrzystość celu.x /= 2
. Lepiej ocalić kolejnego programistę, który akurat spojrzy na twój kod 10-sekundowe podwójne pobranie operacji zmiany, niż niepotrzebnie dowieść, że wiesz, że zmiana była bardziej wydajna bez optymalizacji kompilatora.Wszystkie te zakładają liczby całkowite bez znaku. Prosta zmiana prawdopodobnie nie jest tym, co chcesz podpisać. DanielH również porusza kwestię używania
x *= 0.5
niektórych języków, takich jak ActionScript.źródło
mod 2, test na = 1. nie znamy składni w c. ale to może być najszybsze.
źródło
ogólnie właściwa zmiana dzieli:
czasami jest to wykorzystywane do przyspieszenia programów kosztem przejrzystości. Nie sądzę, że powinieneś to zrobić. Kompilator jest wystarczająco inteligentny, aby automatycznie wykonać przyspieszenie. Oznacza to, że wprowadzenie zmiany nic nie kosztuje kosztem przejrzystości .
Spójrz na tę stronę z praktycznego programowania w C ++.
źródło
(x+128)>>8
Obliczyłaby dla wartościx
nie bliskich maksimum, jak w zwięzły sposób zrobić to bez przesunięcia? Pamiętaj, że(x+128)/256
to nie zadziała. Czy znasz jakieś miłe wypowiedzi, które będą?Oczywiście, jeśli piszesz swój kod dla następnego faceta, który go przeczyta, wybierz przejrzystość „x / 2”.
Jeśli jednak Twoim celem jest prędkość, wypróbuj ją w obie strony i zmierz czas. Kilka miesięcy temu pracowałem nad procedurą splotu bitmapy, która polegała na przejściu przez tablicę liczb całkowitych i podzieleniu każdego elementu przez 2. Zrobiłem różne rzeczy, aby go zoptymalizować, w tym starą sztuczkę polegającą na zamianie „x >> 1” na „x / 2 ”.
Kiedy tak naprawdę zmierzyłem oba sposoby, z zaskoczeniem odkryłem, że x / 2 było szybsze niż x >> 1
To było przy użyciu Microsoft VS2008 C ++ z włączonymi domyślnymi optymalizacjami.
źródło
Pod względem wydajności. Operacje przesunięcia procesora są znacznie szybsze niż dzielenie kodów operacyjnych. Zatem dzielenie przez dwa lub mnożenie przez 2 itd. Wszystkie korzystają z operacji zmiany.
Co do wyglądu. Kiedy jako inżynierowie byliśmy tak przywiązani do kosmetyków, że nawet piękne damy nie używają! :)
źródło
X / Y jest poprawny ... i operator przesuwania „>>” .. jeśli chcemy, aby dwie liczby były liczbą całkowitą, możemy użyć (/) operatora dywidendy. operator shift służy do przesuwania bitów.
x = x / 2; x / = 2; możemy użyć w ten sposób ..
źródło
Podczas gdy x >> 1 jest szybszy niż x / 2, prawidłowe użycie >> w przypadku wartości ujemnych jest nieco bardziej skomplikowane. Wymaga czegoś podobnego do następującego:
źródło