Mnożenie i dzielenie można osiągnąć na przykład za pomocą operatorów bitowych
i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)
i tak dalej.
Czy w rzeczywistości szybsze jest użycie powiedz (i<<3)+(i<<1)
do pomnożenia przez 10 niż i*10
bezpośrednie? Czy jest jakiś rodzaj danych wejściowych, których nie można pomnożyć ani podzielić w ten sposób?
Odpowiedzi:
Krótka odpowiedź: mało prawdopodobne.
Długa odpowiedź: Twój kompilator ma optymalizator, który wie, jak mnożyć tak szybko, jak jest to możliwe w architekturze procesora docelowego. Najlepiej jest wyraźnie powiedzieć kompilatorowi o swoich zamiarach (tzn. I * 2 zamiast i << 1) i pozwolić mu zdecydować, jaka jest najszybsza sekwencja kodu asemblera. Możliwe jest nawet, że sam procesor zaimplementował instrukcję mnożenia jako sekwencję przesunięć i dodawania w mikrokodzie.
Podsumowując - nie marnuj dużo czasu na to. Jeśli masz zamiar się zmienić, to zmień. Jeśli masz zamiar pomnożyć, pomnóż. Rób to, co jest semantycznie klarowne - Twoi współpracownicy podziękują Ci później. Lub, bardziej prawdopodobne, przeklinać cię później, jeśli zrobisz inaczej.
źródło
gcc -O3
x86return i*10
niż z wersji shift . Jako ktoś, kto dużo patrzy na dane wyjściowe kompilatora (zobacz wiele moich odpowiedzi na asm / optymalizację), nie jestem zaskoczony. Są chwile, kiedy może pomóc utrzymać kompilator w jednej ręce , ale nie jest to jeden z nich. gcc jest dobry w matematyce liczb całkowitych, ponieważ jest ważny.millis() >> 2
; Czy byłoby zbyt wiele prosić o podział?i / 32
vsi >> 5
ii / 4
vsi >> 2
na gcc dla kory-a9 (która nie ma podziału sprzętowego) z optymalizacją -O3, a wynikowy montaż był dokładnie taki sam. Najpierw nie lubiłem używać podziałów, ale opisuje to moją intencję, a wyniki są takie same.Tylko konkretny punkt pomiaru: wiele lat temu porównałem dwie wersje mojego algorytmu mieszającego:
i
Na każdej maszynie, na której testowałem, pierwsza była co najmniej tak szybka jak druga. Nieoczekiwanie było to czasem szybsze (np. Na Sun Sparc). Gdy sprzęt nie obsługiwał szybkiego mnożenia (a większość nie wtedy), kompilator konwertuje mnożenie na odpowiednie kombinacje przesunięć i dodawania / dodawania. A ponieważ znał ostateczny cel, czasami mógł to zrobić w mniejszej liczbie instrukcji niż wtedy, gdy wyraźnie pisałeś zmiany i dodawanie / dodawanie.
Zauważ, że było to około 15 lat temu. Miejmy nadzieję, że od tego czasu kompilatory stały się lepsze, więc możesz liczyć na to, że kompilator zrobi właściwą rzecz, prawdopodobnie lepszą niż mógłbyś. (Ponadto, kod wygląda tak C'ish, ponieważ był ponad 15 lat temu. Oczywiście
std::string
użyłbym dzisiaj i iteratorów).źródło
Oprócz wszystkich innych dobrych odpowiedzi tutaj, pozwól mi wskazać kolejny powód, aby nie używać shift, gdy masz na myśli dzielenie lub mnożenie. Nigdy nie widziałem, aby ktoś wprowadzał błąd, zapominając o względnym priorytecie mnożenia i dodawania. Widziałem błędy wprowadzane, gdy programiści serwisowi zapomnieli, że „pomnożenie” przez zmianę jest logicznie zwielokrotnieniem, ale nie składniowym z takim samym pierwszeństwem jak mnożenie.
x * 2 + z
ix << 1 + z
bardzo się różnią!Jeśli pracujesz na liczbach, użyj operatorów arytmetycznych, takich jak
+ - * / %
. Jeśli pracujesz nad tablicami bitów, użyj bitowych operatorów kręcących, takich jak& ^ | >>
. Nie mieszaj ich; Wyrażenie, które ma zarówno tandetne bicie, jak i arytmetykę, jest błędem, który czeka.źródło
Zależy to od procesora i kompilatora. Niektóre kompilatory już optymalizują kod w ten sposób, inne nie. Musisz więc sprawdzać za każdym razem, gdy Twój kod wymaga optymalizacji.
Chyba że rozpaczliwie potrzebujesz optymalizacji, nie szyfrowałbym kodu źródłowego tylko po to, aby zapisać instrukcję asemblera lub cykl procesora.
źródło
>>
operator jest szybszy niż,/
a jeśli podpisane wartości mogą być ujemne, często jest również semantycznie lepszy. Jeśli potrzebna jest wartość, którax>>4
by wytworzyła, jest to o wiele jaśniejsze niżx < 0 ? -((-1-x)/16)-1 : x/16;
i nie wyobrażam sobie, jak kompilator może zoptymalizować to ostatnie wyrażenie do czegoś fajnego.Może, ale nie musi być na twoim komputerze - jeśli cię to obchodzi, zmierz swoje rzeczywiste użycie.
Studium przypadku - od 486 do rdzenia i7
Benchmarking jest bardzo trudny do przeprowadzenia w sensowny sposób, ale możemy spojrzeć na kilka faktów. Z http://www.penguin.cz/~literakl/intel/s.html#SAL i http://www.penguin.cz/~literakl/intel/i.html#IMUL otrzymujemy pojęcie o cyklach zegara x86 potrzebne do przesunięcia arytmetycznego i mnożenia. Powiedzmy, że trzymamy się „486” (najnowszego wymienionego), 32-bitowych rejestrów i natychmiastowych, IMUL zajmuje 13-42 cykli i IDIV 44. Każda SAL zajmuje 2 i dodaje 1, więc nawet przy kilku z nich zmienia się powierzchownie jak zwycięzca.
Obecnie z rdzeniem i7:
(z http://software.intel.com/en-us/forums/showthread.php?t=61481 )
(z jakiegoś napadu Intela)
To daje wyobrażenie o tym, jak daleko zaszło. Ciekawostki związane z optymalizacją - takie jak przesunięcie bitów w porównaniu do
*
- które zostały potraktowane poważnie nawet w latach 90., są teraz przestarzałe. Przesunięcie bitów jest wciąż szybsze, ale w przypadku braku mocy dwóch mul / dz, zanim wykonasz wszystkie swoje zmiany i dodasz wyniki, będzie znowu wolniejszy. Następnie więcej instrukcji oznacza więcej błędów pamięci podręcznej, więcej potencjalnych problemów w przetwarzaniu potokowym, większe wykorzystanie rejestrów tymczasowych może oznaczać więcej zapisywania i przywracania zawartości rejestru ze stosu ... szybko staje się zbyt skomplikowane, aby ostatecznie obliczyć wszystkie wpływy, ale są one głównie negatywne.funkcjonalność w kodzie źródłowym a implementacja
Mówiąc bardziej ogólnie, twoje pytanie jest oznaczone jako C i C ++. Jako języki trzeciej generacji są one specjalnie zaprojektowane, aby ukryć szczegóły podstawowego zestawu instrukcji procesora. Aby spełnić ich Standardy językowe, muszą obsługiwać operacje mnożenia i przenoszenia (i wiele innych), nawet jeśli nie obsługuje tego sprzęt . W takich przypadkach muszą zsyntetyzować wymagany wynik przy użyciu wielu innych instrukcji. Podobnie muszą zapewniać obsługę oprogramowania dla operacji zmiennoprzecinkowych, jeśli procesor go nie ma i nie ma FPU. Nowoczesne procesory wszystkie obsługują
*
i<<
, więc może się to wydawać absurdalnie teoretyczne i historyczne, ale istotne jest to, że swoboda wyboru implementacji przebiega w obie strony: nawet jeśli procesor posiada instrukcję, która implementuje operację wymaganą w kodzie źródłowym w ogólnym przypadku, kompilator może wybierz coś innego, co woli, ponieważ jest to lepsze w konkretnym przypadku, z którym ma do czynienia kompilator.Przykłady (z hipotetycznym językiem asemblera)
Instrukcje takie jak wyłączne lub (
xor
) nie mają związku z kodem źródłowym, ale xor-cokolwiek ze sobą usuwa wszystkie bity, więc można go użyć do ustawienia czegoś na 0. Kod źródłowy sugerujący, że adresy pamięci nie wymagają użycia.Tego rodzaju włamania były używane tak długo, jak długo istniały komputery. Na początku 3GLs, aby zabezpieczyć programistę, wyjście kompilatora musiało zaspokoić istniejącego hardcorowego, optymalizującego ręcznie dewelopera w asemblerze. społeczność, że wygenerowany kod nie był wolniejszy, bardziej szczegółowy lub w inny sposób gorszy. Kompilatory szybko przyjęły wiele świetnych optymalizacji - stały się lepiej scentralizowanym magazynem, niż mógłby to być każdy programista w języku asemblera, choć zawsze istnieje szansa, że przegapią konkretną optymalizację, która jest kluczowa w konkretnym przypadku - ludzie mogą czasem wykręć to i szukaj czegoś lepszego, podczas gdy kompilatory robią tak, jak im powiedzono, dopóki ktoś nie wróci do nich tym doświadczeniem.
Tak więc, nawet jeśli przesuwanie i dodawanie jest jeszcze szybsze na określonym sprzęcie, pisarz kompilatora prawdopodobnie zadziałał dokładnie wtedy, gdy jest bezpieczny i korzystny.
Konserwowalność
Jeśli twój sprzęt ulegnie zmianie, możesz go ponownie skompilować, a on spojrzy na docelowy procesor i dokona innego najlepszego wyboru, podczas gdy raczej nie będziesz chciał ponownie przeglądać swoich „optymalizacji” lub listy, które środowiska kompilacji powinny używać mnożenia, a które powinny się zmieniać. Pomyśl o wszystkich „optymalizacjach” o przesunięciu nieco dwóch bitów, napisanych ponad 10 lat temu, które spowalniają kod, w którym się znajdują, ponieważ działa na nowoczesnych procesorach ...!
Na szczęście dobre kompilatory, takie jak GCC, mogą zazwyczaj zastąpić serię przesunięć bitowych i arytmetyki bezpośrednim zwielokrotnieniem, gdy włączona jest jakakolwiek optymalizacja (tj.
...main(...) { return (argc << 4) + (argc << 2) + argc; }
->imull $21, 8(%ebp), %eax
), więc rekompilacja może pomóc nawet bez poprawiania kodu, ale nie jest to gwarantowane.Dziwny kod bitshiftingu, który implementuje mnożenie lub dzielenie, jest o wiele mniej wyrazisty w stosunku do tego, co próbujesz osiągnąć koncepcyjnie, więc inni programiści będą tym zdezorientowani, a zdezorientowany programista bardziej prawdopodobne jest wprowadzenie błędów lub usunięcie czegoś niezbędnego w celu przywrócenia zdrowego rozsądku. Jeśli robisz rzeczy nieoczywiste tylko wtedy, gdy są naprawdę namacalnie korzystne, a następnie dobrze je dokumentujesz (ale i tak nie dokumentujesz innych rzeczy, które są intuicyjne), wszyscy będą szczęśliwsi.
Rozwiązania ogólne a rozwiązania częściowe
Jeśli masz trochę dodatkowej wiedzy, na przykład, że
int
naprawdę będziesz przechowywać tylko wartościx
,y
az
następnie możesz być w stanie wypracować instrukcje, które działają dla tych wartości i uzyskać wynik szybciej niż wtedy, gdy kompilator nie ma ten wgląd i wymaga implementacji, która działa dla wszystkichint
wartości. Rozważ na przykład swoje pytanie:Ilustrujesz mnożenie, ale co powiesz na podział?
Zgodnie ze standardem C ++ 5.8:
Zatem przesunięcie bitów ma wynik zdefiniowany w implementacji, gdy
x
jest ujemny: może nie działać w ten sam sposób na różnych komputerach. Ale/
działa o wiele bardziej przewidywalnie. (Może to również nie być całkowicie spójne, ponieważ różne maszyny mogą mieć różne reprezentacje liczb ujemnych, a zatem różne zakresy, nawet jeśli ta sama liczba bitów tworzy tę reprezentację.)Możesz powiedzieć: „Nie obchodzi mnie to…
int
przechowywanie wieku pracownika, nigdy nie może być negatywne”. Jeśli masz taki szczególny wgląd, to tak - Twoja>>
bezpieczna optymalizacja może zostać pominięta przez kompilator, o ile nie zrobisz tego wprost w kodzie. Jest to jednak ryzykowne i rzadko przydatne, ponieważ przez większość czasu nie będziesz mieć takiego wglądu, a inni programiści pracujący nad tym samym kodem nie będą wiedzieć, że postawiłeś dom na pewne niezwykłe oczekiwania dotyczące danych, które „ Zajmę się ... to, co wydaje się całkowicie bezpieczną zmianą, może się nie udać z powodu twojej „optymalizacji”.Tak ... jak wspomniano powyżej, liczby ujemne mają zachowanie zdefiniowane w implementacji, gdy są „podzielone” przez przesunięcie bitów.
źródło
intVal>>1
będą miały tę samą semantykę, która różni się od tychintVal/2
w sposób, który jest czasem użyteczny. Jeśli trzeba obliczyć w przenośny sposób wartość, którą przyniosłyby zwykłe architekturyintVal >> 1
, wyrażenie musiałoby być bardziej skomplikowane i trudniejsze do odczytania, i prawdopodobnie wygenerowałby znacznie gorszy kod niż wygenerowanyintVal >> 1
.Właśnie wypróbowałem na moim komputerze kompilując to:
Podczas demontażu generuje dane wyjściowe:
Ta wersja jest szybsza niż twój zoptymalizowany ręcznie kod z czystym przesunięciem i dodawaniem.
Naprawdę nigdy nie wiesz, co wymyśli kompilator, więc lepiej po prostu napisać normalne mnożenie i pozwolić mu zoptymalizować to, co chce, z wyjątkiem bardzo dokładnych przypadków, w których wiesz, że kompilator nie może zoptymalizować.
źródło
vector<T>::size()
. Mój kompilator był dość stary! :)Przesuwanie jest zazwyczaj dużo szybsze niż mnożenie na poziomie instrukcji, ale możesz tracić czas na przedwczesne optymalizacje. Kompilator może wykonywać te optymalizacje w czasie kompilacji. Zrób to sam, wpłynie na czytelność i prawdopodobnie nie wpłynie na wydajność. Prawdopodobnie warto robić takie rzeczy tylko wtedy, gdy profilujesz i uważasz, że jest to wąskie gardło.
W rzeczywistości sztuczka z podziałem, znana jako „podział magiczny”, może przynieść ogromne korzyści. Ponownie powinieneś najpierw profilować, aby zobaczyć, czy jest to potrzebne. Ale jeśli go użyjesz, znajdziesz przydatne programy, które pomogą Ci dowiedzieć się, jakie instrukcje są potrzebne dla tej samej semantyki podziału. Oto przykład: http://www.masm32.com/board/index.php?topic=12421.0
Przykład, który podniosłem z wątku OP na MASM32:
Wygeneruje:
źródło
Instrukcje przesunięcia i mnożenia liczb całkowitych mają podobną wydajność na większości współczesnych procesorów - instrukcje mnożenia liczb całkowitych były stosunkowo powolne w latach 80., ale generalnie nie jest to już prawdą. Instrukcje mnożenia liczb całkowitych mogą mieć większe opóźnienia , więc nadal mogą zdarzyć się przypadki, w których preferowane jest przesunięcie. To samo dotyczy przypadków, w których możesz utrzymywać więcej jednostek wykonawczych zajętych (chociaż może to zmniejszyć obie strony).
Dzielenie liczb całkowitych jest jednak nadal stosunkowo wolne, więc stosowanie przesunięcia zamiast dzielenia przez potęgę 2 to nadal wygrana, a większość kompilatorów zastosuje to jako optymalizację. Należy jednak pamiętać, że aby ta optymalizacja była prawidłowa, dywidenda musi być niepodpisana lub musi być znana jako dodatnia. W przypadku dywidendy ujemnej przesunięcie i podział nie są równoważne!
Wynik:
Więc jeśli chcesz pomóc kompilatorowi, upewnij się, że zmienna lub wyrażenie w dywidendzie jest jawnie niepodpisane.
źródło
Zależy to całkowicie od urządzenia docelowego, języka, celu itp.
Chrupanie pikseli w sterowniku karty graficznej? Bardzo prawdopodobne, że tak!
Aplikacja biznesowa .NET dla Twojego działu? Absolutnie nie ma powodu, aby w to zaglądać.
W przypadku wysokowydajnej gry na urządzenie mobilne warto przyjrzeć się temu, ale dopiero po przeprowadzeniu łatwiejszych optymalizacji.
źródło
Nie rób, chyba że jest to absolutnie konieczne, a zamiar kodu wymaga przesunięcia, a nie mnożenia / dzielenia.
W typowym dniu - możesz potencjalnie zaoszczędzić kilka cykli maszyny (lub stracić, ponieważ kompilator wie lepiej, co zoptymalizować), ale koszt nie jest tego wart - spędzasz czas na drobnych szczegółach, a nie na faktycznej pracy, utrzymanie kodu staje się trudniejsze i twoi współpracownicy cię przeklną.
Być może trzeba to zrobić w przypadku obliczeń o dużym obciążeniu, w których każdy zapisany cykl oznacza minuty czasu wykonywania. Ale powinieneś optymalizować jedno miejsce na raz i przeprowadzać testy wydajności za każdym razem, aby sprawdzić, czy naprawdę przyspieszysz lub złamałeś logikę kompilatorów.
źródło
O ile wiem w niektórych maszynach mnożenie może wymagać od 16 do 32 cykli maszynowych. Więc tak , w zależności od typu maszyny, operatorzy Bitshift są szybsze niż mnożenie / dzielenie.
Jednak niektóre maszyny mają procesor matematyczny, który zawiera specjalne instrukcje dotyczące mnożenia / dzielenia.
źródło
Zgadzam się z zaznaczoną odpowiedzią Drew Hala. Odpowiedź mogłaby jednak przynieść dodatkowe uwagi.
Dla zdecydowanej większości programistów procesor i kompilator nie są już odpowiednie do pytania. Większość z nas daleko wykracza poza 8088 i MS-DOS. Być może dotyczy to tylko tych, którzy wciąż pracują nad wbudowanymi procesorami ...
W moim oprogramowaniu do matematyki należy używać Math (add / sub / mul / div). Podczas konwersji między typami danych należy używać Shift, np. ushort do bajtu jako n >> 8, a nie n / 256.
źródło
W przypadku liczb całkowitych ze znakiem i prawej zmiany vs podziału może to mieć znaczenie. W przypadku liczb ujemnych zaokrąglenie zmiany zaokrągla się w kierunku ujemnej nieskończoności, natomiast podział zaokrągla w kierunku zera. Oczywiście kompilator zmieni podział na coś tańszego, ale zwykle zmieni go na coś, co ma takie samo zachowanie zaokrąglania jak podział, ponieważ albo nie jest w stanie udowodnić, że zmienna nie będzie ujemna, albo po prostu nie opieka. Więc jeśli możesz udowodnić, że liczba nie będzie ujemna lub jeśli nie obchodzi Cię, w jaki sposób będzie ona zaokrąglać, możesz przeprowadzić tę optymalizację w sposób, który bardziej prawdopodobne jest, aby coś zmienić.
źródło
unsigned
Test Pythona przeprowadzający to samo pomnożenie 100 milionów razy w stosunku do tych samych liczb losowych.
Tak więc, dokonując zmiany zamiast mnożenia / dzielenia przez potęgę dwóch w pythonie, istnieje niewielka poprawa (~ 10% dla podziału; ~ 1% dla mnożenia). Jeśli nie ma potęgi dwóch, prawdopodobnie nastąpi znaczne spowolnienie.
Znowu te #s zmienią się w zależności od twojego procesora, twojego kompilatora (lub interpretera - zrobiłem to w pythonie dla uproszczenia).
Jak w przypadku wszystkich innych, nie optymalizuj przedwcześnie. Napisz bardzo czytelny kod, profil, jeśli nie jest wystarczająco szybki, a następnie spróbuj zoptymalizować wolne części. Pamiętaj, że twój kompilator jest znacznie lepszy w optymalizacji niż ty.
źródło
Istnieją optymalizacje, których kompilator nie może wykonać, ponieważ działają one tylko dla ograniczonego zestawu danych wejściowych.
Poniżej znajduje się przykładowy kod c ++, który może wykonać szybszy podział, wykonując 64-bitowe „Mnożenie przez odwrotność”. Zarówno licznik, jak i mianownik muszą znajdować się poniżej pewnego progu. Zauważ, że musi być skompilowany, aby użyć instrukcji 64-bitowych, aby był w rzeczywistości szybszy niż normalne dzielenie.
źródło
Myślę, że w jednym przypadku, który chcesz pomnożyć lub podzielić przez potęgę dwóch, nie można pomylić się z użyciem operatorów bitshift, nawet jeśli kompilator konwertuje je na MUL / DIV, ponieważ niektóre procesory mikrokodują (tak naprawdę makro) i tak, więc w tych przypadkach osiągniesz poprawę, szczególnie jeśli przesunięcie jest większe niż 1. Lub bardziej wyraźnie, jeśli CPU nie ma operatorów przesunięcia bitów, i tak będzie to MUL / DIV, ale jeśli CPU ma operatory bitshift, unikamy gałęzi mikrokodu, a to kilka instrukcji mniej.
Piszę teraz trochę kodu, który wymaga wielu operacji podwajania / zmniejszania o połowę, ponieważ działa on na gęstym drzewie binarnym, i jest jeszcze jedna operacja, która, jak podejrzewam, może być bardziej optymalna niż dodatek - lewy (potęga dwóch razy ) zmiana z dodatkiem. Można to zastąpić przesunięciem w lewo i xorem, jeśli przesunięcie jest szersze niż liczba bitów, które chcesz dodać, łatwym przykładem jest (i << 1) ^ 1, co dodaje jeden do podwojonej wartości. Nie dotyczy to oczywiście przesunięcia w prawo (potęga dwóch podziałów), ponieważ tylko przesunięcie w lewo (mały endian) wypełnia lukę zerami.
W moim kodzie te mnożą się / dzielą przez dwa, a moce dwóch operacji są bardzo intensywnie używane, a ponieważ formuły są już dość krótkie, każda instrukcja, którą można wyeliminować, może być znaczącym zyskiem. Jeśli procesor nie obsługuje tych operatorów zmiany bitów, zysk nie nastąpi, ale nie nastąpi utrata.
Ponadto w algorytmach, które piszę, reprezentują one wizualnie ruchy, które występują, więc w tym sensie są bardziej wyraźne. Lewa strona drzewa binarnego jest większa, a prawa jest mniejsza. Ponadto w moim kodzie liczby nieparzyste i parzyste mają szczególne znaczenie, a wszystkie dzieci leworęczne w drzewie są nieparzyste, a wszystkie dzieci praworęczne i korzeń są parzyste. W niektórych przypadkach, których jeszcze nie spotkałem, ale może nawet nie pomyślałem o tym, x & 1 może być bardziej optymalną operacją w porównaniu do x% 2. x i 1 dla liczby parzystej da zero, ale da 1 dla liczby nieparzystej.
Idąc nieco dalej niż zwykła identyfikacja nieparzysta / parzysta, jeśli otrzymam zero dla x i 3, wiem, że 4 jest współczynnikiem naszej liczby, i to samo dla x% 7 dla 8 i tak dalej. Wiem, że te przypadki prawdopodobnie mają ograniczoną użyteczność, ale miło jest wiedzieć, że można uniknąć operacji modułu i zamiast tego użyć operacji logiki bitowej, ponieważ operacje bitowe są prawie zawsze najszybsze i najmniej prawdopodobne, że będą dwuznaczne dla kompilatora.
Właściwie wynajduję pole gęstych drzew podwójnych, więc spodziewam się, że ludzie mogą nie zrozumieć wartości tego komentarza, ponieważ bardzo rzadko ludzie chcą dokonywać faktoryzacji tylko na potęgach dwóch, lub tylko potęgować / dzielić potęgi dwóch.
źródło
To, czy rzeczywiście jest szybsze, zależy od faktycznie używanego sprzętu i kompilatora .
źródło
Jeśli porównasz dane wyjściowe dla składni x + x, x * 2 i x << 1 w kompilatorze gcc, uzyskasz ten sam wynik w zestawie x86: https://godbolt.org/z/JLpp0j
Możesz więc uznać gcc za wystarczająco inteligentnego, aby określić własne najlepsze rozwiązanie niezależnie od tego, co wpisałeś.
źródło
Ja też chciałem sprawdzić, czy uda mi się pokonać Dom. jest to bardziej ogólna bitowa liczba dowolna przez mnożenie dowolnej liczby. utworzone przeze mnie makra są około 25% więcej lub dwa razy wolniejsze niż normalne * mnożenie. jak powiedzieli inni, jeśli jest blisko wielokrotności 2 lub składa się z kilku wielokrotności 2, możesz wygrać. jak X * 23 złożony z (X << 4) + (X << 2) + (X << 1) + X będzie wolniejszy niż X * 65 złożony z (X << 6) + X.
źródło