Czy

Czytam książkę, w której autor mówi, że if( a < 901 ) jest szybszy niż if( a <= 900 ).

Nie do końca tak jak w tym prostym przykładzie, ale występują niewielkie zmiany wydajności w złożonym kodzie pętli. Przypuszczam, że to ma coś wspólnego z wygenerowanym kodem maszynowym na wypadek, gdyby to była prawda.

Author: ROMANIA_engineer, 2012-08-27

13 answers

Nie, Nie będzie szybszy na większości architektur. Nie podałeś, ale na x86 wszystkie porównania całkowe będą zazwyczaj implementowane w dwóch instrukcjach maszynowych:

  • a test lub cmp instrukcja, która ustawia EFLAGS
  • i Jcc (jump) Instrukcja , w zależności od typu porównania (i układu kodu):
    • jne - Jump if not equal -- > ZF = 0
    • jz - Jump if zero (equal) -- > ZF = 1
    • jg - skok jeśli greater -- > ZF = 0 and SF = OF
    • (itd...)

Przykład (edytowany dla zwięzłości) skompilowany z $ gcc -m32 -S -masm=intel test.c

    if (a < b) {
        // Do something 1
    }

Kompiluje do:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jge     .L2                          ; jump if a is >= b
    ; Do something 1
.L2:

I

    if (a <= b) {
        // Do something 2
    }

Kompiluje do:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jg      .L5                          ; jump if a is > b
    ; Do something 2
.L5:

Więc jedyną różnicą między nimi jest jg A jge Instrukcja. To zajmie tyle samo czasu.


Chciałbym odnieść się do komentarza, że nic nie wskazuje na to, że różne instrukcje skoku biorą tyle samo czasu. Ta jest trochę trudna do odpowiedzi, ale oto, co mogę dać: w zestaw instrukcji Intel Reference, wszystkie są zgrupowane w jednej wspólnej instrukcji, Jcc (skok, jeśli warunek jest spełniony). To samo grupowanie jest wykonane razem w podręczniku optymalizacji , W dodatku C. opóźnienie i przepustowość.

Latency - liczba cykli zegara, które są wymagane dla wykonanie rdzenia aby zakończyć wykonanie wszystkich µops, które tworzą Instrukcja.

Przepustowość - liczba cykli zegara wymaganych do poczekaj, aż porty wydające będą mogły zaakceptować tę samą instrukcję jeszcze raz. Dla wielu instrukcji przepustowość instrukcji może być znacznie mniej niż jego opóźnienie

Wartości dla Jcc to:

      Latency   Throughput
Jcc     N/A        0.5

Z następującym przypisem na Jcc:

7) wybór instrukcji skoku warunkowego powinien być oparte na zaleceniu sekcji 3.4.1, "Optymalizacja przewidywania gałęzi", w celu poprawy przewidywalności gałęzi. Gdy branches are predicted successful, the latency of jcc is effectively zero.

Więc nic w dokumentach Intel nigdy nie traktuje jednej instrukcji inaczej niż innych.

Jeśli pomyśli się o rzeczywistych obwodach używanych do realizacji instrukcji, można założyć, że byłyby proste i / lub bramki na różnych bity w EFLAGS, aby określić, czy warunki są spełnione. Nie ma zatem powodu, aby Instrukcja testująca dwa bity zajmowała więcej lub mniej czasu niż jedna testująca tylko jeden (ignorując Opóźnienie propagacji bramki, które jest znacznie mniejsze niż okres zegara.)


Edit: Zmiennoprzecinkowy

Dotyczy to również zmiennoprzecinkowego x87: (prawie taki sam kod jak powyżej, ale z double zamiast int.)

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
        fstp    st(0)
        seta    al                     ; Set al if above (CF=0 and ZF=0).
        test    al, al
        je      .L2
        ; Do something 1
.L2:

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; (same thing as above)
        fstp    st(0)
        setae   al                     ; Set al if above or equal (CF=0).
        test    al, al
        je      .L5
        ; Do something 2
.L5:
        leave
        ret
 1580
Author: Jonathon Reinhart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 20:28:37

Historycznie (mówimy o latach 80. i wczesnych latach 90.), istniały niektóre architektury, w których to było prawdą. Głównym problemem jest to, że porównywanie liczb całkowitych jest z natury zaimplementowane poprzez odejmowanie liczb całkowitych. Prowadzi to do następujących przypadków.

Comparison     Subtraction
----------     -----------
A < B      --> A - B < 0
A = B      --> A - B = 0
A > B      --> A - B > 0

Teraz, Kiedy A < B odejmowanie musi pożyczyć wysoki bit, aby odejmowanie było poprawne, tak jak nosisz i pożyczasz podczas dodawania i odejmowania ręcznie. Ten" pożyczony " bit był zwykle określany jako przenoszenie bitu i można by go przetestować za pomocą instrukcji branch. Drugi bit o nazwie bit zerowy byłby ustawiony, gdyby odejmowanie było identyczne zero, co implikuje równość.

Były zwykle co najmniej dwie warunkowe instrukcje odgałęzienia, jedna do odgałęzienia na bitach przenoszenia i jedna na bitach zerowych.

Teraz, aby przejść do sedna sprawy, rozszerzmy poprzednią tabelę o wyniki carry i zero bit.

Comparison     Subtraction  Carry Bit  Zero Bit
----------     -----------  ---------  --------
A < B      --> A - B < 0    0          0
A = B      --> A - B = 0    1          1
A > B      --> A - B > 0    1          0

Więc, wdrożenie gałęzi dla A < B można wykonać w jednej instrukcji, ponieważ bit carry jest jasny tylko w tym przypadku , czyli

;; Implementation of "if (A < B) goto address;"
cmp  A, B          ;; compare A to B
bcz  address       ;; Branch if Carry is Zero to the new address

Ale, jeśli chcemy zrobić porównanie mniej niż równe, musimy wykonać dodatkowe sprawdzenie flagi zero, aby złapać przypadek równości.

;; Implementation of "if (A <= B) goto address;"
cmp A, B           ;; compare A to B
bcz address        ;; branch if A < B
bzs address        ;; also, Branch if the Zero bit is Set

Tak więc, na niektórych maszynach, użycie porównania "mniej niż" może zapisaćjedną instrukcję maszynową . Było to istotne w erze sub-megaherców prędkości procesora i 1: 1 CPU-to-memory stosunek prędkości, ale to to dziś prawie zupełnie nieistotne.

 563
Author: Lucas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 05:54:09

Zakładając, że mówimy o wewnętrznych typach liczb całkowitych, nie ma możliwości, aby jeden był szybszy od drugiego. Są semantycznie identyczne. Oboje proszą kompilatora, aby zrobił dokładnie to samo. Tylko strasznie zepsuty kompilator wygenerowałby gorszy kod dla jednego z nich.

Jeśli istniała platforma, na której < była szybsza niż <= dla prostych typów całkowitych, kompilator powinien Zawsze konwertować <= na < dla stałych. Każdy kompilator, który nie byłby to po prostu zły kompilator(dla tej platformy).

 85
Author: David Schwartz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 02:58:27

Widzę, że żadne z nich nie jest szybsze. Kompilator generuje ten sam kod maszynowy w każdym stanie z inną wartością.

if(a < 901)
cmpl  $900, -4(%rbp)
jg .L2

if(a <=901)
cmpl  $901, -4(%rbp)
jg .L3

Mój przykład {[2] } pochodzi z GCC na platformie x86_64 na Linuksie.

Autorzy kompilatorów to całkiem mądrzy ludzie, którzy myślą o tych rzeczach i wielu innych, których większość z nas uważa za pewnik.

Zauważyłem, że jeśli nie jest stałą, to w obu przypadkach generowany jest ten sam kod maszynowy.

int b;
if(a < b)
cmpl  -4(%rbp), %eax
jge   .L2

if(a <=b)
cmpl  -4(%rbp), %eax
jg .L3
 66
Author: Adrian Cornish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 05:48:55

Dla kodu zmiennoprzecinkowego porównanie

int compare_strict(double a, double b) { return a < b; }

Na PowerPC, najpierw wykonuje porównanie zmiennoprzecinkowe (które aktualizuje cr, rejestr warunków), a następnie przenosi rejestr warunków do GPR, przesuwa bit "porównano mniej niż" na miejsce, a następnie powraca. Potrzeba czterech instrukcji.

Rozważmy teraz tę funkcję zamiast:

int compare_loose(double a, double b) { return a <= b; }

To wymaga ta sama praca co compare_strict powyżej, ale teraz są dwa bity zainteresowania: "było mniej niż" i " było równe."Wymaga to dodatkowej instrukcji (cror - condition register bitwise OR), aby połączyć te dwa bity w jeden. Więc compare_loose wymaga pięciu instrukcji, podczas gdy compare_strict wymaga czterech.

Można by pomyśleć, że kompilator mógłby zoptymalizować drugą funkcję w ten sposób:

int compare_loose(double a, double b) { return ! (a > b); }

To jednak nieprawidłowo obsłuży Nan. NaN1 <= NaN2 i NaN1 > NaN2 muszą obie Oceniać na false.

 49
Author: ridiculous_fish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 18:38:13

Może autor tej nienazwanej książki przeczytał, że a > 0 działa szybciej niż {[1] } i uważa, że to prawda.

Ale to dlatego, że 0 jest zaangażowany (ponieważ CMP może, w zależności od architektury, zastąpić np. OR), a nie z powodu <.

 34
Author: glglgl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 13:05:52

Gdyby to była prawda, kompilator mógłby trywialnie zoptymalizować a b), a więc nawet gdyby samo porównanie było rzeczywiście wolniejsze, przy wszystkich, poza najbardziej naiwnym kompilatorze, nie zauważyłbyś różnicy.

 29
Author: Eliot Ball,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-30 00:22:19

Mają taką samą prędkość. Może w jakiejś specjalnej architekturze to co powiedział jest słuszne, ale w rodzinie x86 przynajmniej wiem, że są takie same. Ponieważ w tym celu procesor zrobi odejmowanie (a-b), a następnie sprawdzi flagi rejestru flag. Dwa bity tego rejestru nazywane są ZF (Flaga zerowa) i SF( flaga znakowa) i odbywa się to w jednym cyklu, ponieważ zrobi to za pomocą jednej operacji maski.

 15
Author: Masoud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 05:52:00

Byłoby to w dużym stopniu zależne od podstawowej architektury, do której C jest kompilowany. Niektóre procesory i architektury mogą mieć wyraźne instrukcje równe lub mniejsze niż i równe, które wykonują się w różnej liczbie cykli.

Byłoby to dość nietypowe, ponieważ kompilator mógłby to obejść, czyniąc to nieistotnym.

 13
Author: Telgin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 02:15:06

TL; DR odpowiedz

Dla większości kombinacji architektury, kompilatora i języka nie będzie to szybsze.

Pełna odpowiedź

Inne odpowiedzi skupiły się na architekturze x86 i nie znam architektury ARM (którą wydaje się być twój przykładowy asembler) na tyle dobrze, aby skomentować wygenerowany kod, ale jest to przykład mikro-optymalizacji {2]} która jest bardzo specyficzna dla architektury i jest jako może być antyoptymalizacją, ponieważ ma być optymalizacją .

Jako taki sugerowałbym, że tego rodzaju mikro-optymalizacja jest przykładem kultu ładunku programowania, a nie najlepszej praktyki inżynierii oprogramowania.

Są prawdopodobnieniektóre architektury, gdzie jest to optymalizacja, ale znam przynajmniej jedną architekturę, w której może być odwrotnie. Architektura Transputera miała tylko kod maszynowy instrukcje dla równe i większe lub równe , więc wszystkie porównania musiały być zbudowane z tych prymitywów.

Nawet wtedy, prawie we wszystkich przypadkach, kompilator mógł zamówić instrukcje oceny w taki sposób, że w praktyce żadne porównanie nie miało żadnej przewagi nad jakimkolwiek innym. W najgorszym przypadku może być konieczne dodanie odwrotnej instrukcji (REV), aby zamienić dwa górne elementy stosu . Była to Instrukcja jednobajtowa, która cykl do uruchomienia, więc miał najmniejszy możliwy napowietrzny.

To, czy taka mikro-optymalizacja jest optymalizacją czy antyoptymalizacją zależy od konkretnej architektury, której używasz, więc zwykle jest złym pomysłem, aby nabrać nawyku korzystania z mikrooptymalizacji specyficznych dla architektury, w przeciwnym razie możesz instynktownie użyć takiej, gdy jest to niewłaściwe, i wygląda na to, że jest to dokładnie to, co książka, którą czytasz, zaleca.

 11
Author: Mark Booth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-14 11:28:29

Nie powinieneś być w stanie zauważyć różnicy, nawet jeśli istnieje. Poza tym, w praktyce będziesz musiał wykonać dodatkowe a + 1 LUB a - 1, aby warunek stał, chyba że użyjesz jakichś stałych magicznych, co jest bardzo złą praktyką.

 6
Author: shinkou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 02:17:05

Można powiedzieć, że wiersz jest poprawny w większości języków skryptowych, ponieważ dodatkowy znak powoduje nieco wolniejsze przetwarzanie kodu. Jednak, jak wskazała najwyższa odpowiedź, nie powinno to mieć żadnego wpływu w C++, a cokolwiek robi się za pomocą języka skryptowego, prawdopodobnie nie jest to związane z optymalizacją.

 3
Author: Ecksters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-29 02:47:03

W rzeczywistości będą one dokładnie takie same prędkości, ponieważ na poziomie montażu obie biorą jedną linię. Np.:

  • jl ax,dx (skoki jeśli AX jest mniejszy niż DX)
  • jle ax,dx (przeskakuje, jeśli AX jest mniejszy lub równy DX)
Więc nie, ani nie jest szybszy. Ale jeśli chcesz być naprawdę techniczny, myślę, że gdybyś sprawdził to na poziomie prądu elektronowego, byłoby to nieco szybsze, ale nie w pobliżu prędkości, którą zauważyłbyś.
 -5
Author: Kevin Usher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 05:57:00