it-swarm-eu.dev

Vyhněte se Postfix Increment Operator

Četl jsem, že bych měl vyhnout se operátoru přírůstku postfix z důvodů výkonu (v některých případech).

Ale neovlivní to čitelnost kódu? Dle mého názoru:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

Vypadá lépe než:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

Ale to je pravděpodobně jen zvyk. Je pravda, že jsem mnoho lidí neviděl ++i.

Je v tomto případě výkon, který špatně obětuje čitelnost? Nebo jsem jen slepý a ++i Je čitelnější než i++?

25
Mateen Ulhaq

Fakta:

  1. i ++ a ++ i jsou stejně snadno čitelné. Nelíbí se vám jeden, protože na to nejste zvyklí, ale v zásadě není nic, co byste mohli interpretovat jako, takže už není třeba číst ani psát.

  2. V alespoň některých případech bude operátor postfixu méně efektivní.

  3. Avšak v 99,99% případech na tom nebude záležet, protože (a) bude stejně jednat s jednoduchým nebo primitivním typem a je to jen problém, pokud kopíruje velký objekt (b) nebude to ve výkonu kritická část kódu (c) nevíte, jestli jej kompilátor optimalizuje nebo ne, může to udělat.

  4. Doporučuji tedy používat předponu, pokud nepotřebujete postfix, což je dobrý zvyk, do kterého se dostanete, jen proto, že (a) je to dobrý zvyk být přesný s jinými věcmi a (b) jednou za modrý měsíc budete chtít použít postfix a dostat to špatně: pokud vždy píšete, co máte na mysli, je to méně pravděpodobné. Vždy existuje kompromis mezi výkonem a optimalizací.

Měli byste používat zdravý rozum a neměli byste optimalizovat mikroorganismy, dokud to nebudete potřebovat, ale neměli byste být ani kvůli tomu zjevně neefektivní. Typicky to znamená: za prvé, vyloučit jakoukoli konstrukci kódu, která je nepřijatelně neefektivní dokonce i v časově nekritickém kódu (obvykle něco, co představuje základní koncepční chybu, jako předávání 500MB objektů hodnotou bez důvodu); a za druhé, ze všech ostatních způsobů psaní kódu vyberte nejjasnější.

Zde se však domnívám, že odpověď je jednoduchá: věřím, že psaní předpony, pokud konkrétně nepotřebujete postfix, je (a) velmi okrajově jasnější a (b) velmi okrajově pravděpodobnější, že bude efektivnější, takže byste to měli vždy psát ve výchozím nastavení, ale neboj se o to, pokud zapomenete.

Před šesti měsíci jsem si myslel to samé jako vy, že i ++ bylo přirozenější, ale je to čistě to, na co jste zvyklí.

Úpravy 1: Scott Meyers, v "Efektivnější C++", kterému obecně věřím v tuto věc, říká, že byste se obecně měli vyhnout použití postfixového operátora na uživatelem definovaných typech (protože jedinou rozumnou implementací funkce postfixového přírůstku je vytvořit kopii objektu, zavolejte funkci přírůstku předpony a proveďte přírůstek a vraťte kopii, ale operace kopírování mohou být drahé).

Nevíme tedy, zda existují nějaká obecná pravidla týkající se (a) toho, zda je to dnes pravda, (b) zda se to také vztahuje (méně) na vnitřní typy (c), zda byste měli používat „++“ na nic víc než lehká iterátorská třída vůbec. Ale ze všech důvodů, které jsem popsal výše, nezáleží, dělejte to, co jsem řekl dříve.

Úpravy 2: Jedná se o obecnou praxi. Pokud si myslíte, že to v některých konkrétních případech záleží, měli byste to profilovat a vidět. Profilování je snadné a levné a funguje. Odpočítávání od prvních principů, co je třeba optimalizovat, je těžké a drahé a nefunguje.

58
Jack V.

Vždy naprogramujte nejprve programátor a druhý počítač.

Pokud existuje rozdíl ve výkonu, poté, co kompilátor vrhl své odborné oko na váš kód, A můžete jej změřit A na tom záleží - pak to můžete změnit.

62
Martin Beckett

GCC vytváří stejný strojový kód pro obě smyčky.

C kód

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

Montážní kód (s mými komentáři)

    cstring
LC0:
    .ascii "i = %d\12\0"
    .text
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    call    L9
"L00000000001$pb":
L9:
    popl    %ebx
    movl    $0, -16(%ebp)  // -16(%ebp) is "i" for the first loop 
    jmp L2
L3:
    movl    -16(%ebp), %eax   // move i for the first loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub    // call printf
    leal    -16(%ebp), %eax  // make the eax register point to i
    incl    (%eax)           // increment i
L2:
    cmpl    $41, -16(%ebp)  // compare i to the number 41
    jle L3              // jump to L3 if less than or equal to 41
    movl    $0, -12(%ebp)   // -12(%ebp) is "i" for the second loop  
    jmp L5
L6:
    movl    -12(%ebp), %eax   // move i for the second loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub     // call printf
    leal    -12(%ebp), %eax  // make eax point to i
    incl    (%eax)           // increment i
L5:
    cmpl    $41, -12(%ebp)   // compare i to 41 
    jle L6               // jump to L6 if less than or equal to 41
    movl    $0, %eax
    addl    $36, %esp
    popl    %ebx
    leave
    ret
    .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
    .indirect_symbol _printf
    hlt ; hlt ; hlt ; hlt ; hlt
    .subsections_via_symbols
13
bit-twiddler

Nedělejte si starosti s výkonem, řekněte 97% času. Předčasná optimalizace je kořenem všeho zla.

- Donald Knuth

Teď, když to není v cestě, udělejme si zdravý výběr :

  • ++i: přírůstek předpony , zvýší aktuální hodnotu a získá výsledek
  • i++: přírůstek postfixu , zkopírujte hodnotu, zvýší aktuální hodnotu, získá kopii

Pokud není vyžadována kopie staré hodnoty, použití přírůstku postfixu je kulatým způsobem, jak věci udělat.

Nepřesnost pochází z lenivosti, vždy používejte konstrukci, která vyjadřuje váš záměr nejpřímějším způsobem, je menší šance, než by budoucí správce mohl nepochopit váš původní záměr.

I když je to (opravdu) menší, jsou chvíle, kdy jsem byl opravdu zmaten čtením kódu: Opravdu jsem přemýšlel, zda se úmysl a skutečný expres shodovaly, a samozřejmě, po několika měsících, oni (nebo já) ani si nepamatoval ...

Nezáleží tedy na tom, zda to pro vás vypadá, nebo ne. Objetí POLIBEK . Za pár měsíců se budete vyhýbat svým starým praktikám.

12
Matthieu M.

V C++ můžete mohli podstatně změnit výkon, pokud se jedná o přetížení operátora, zejména pokud píšete dočasný kód a nevíte, v jakých iterátorech může být předán. Logika za jakýmkoli iterátorem X může být jak podstatný, tak významný - to znamená, že kompilátor je pomalý a nevyčíslitelný.

Ale to není případ C, kde víte, že to bude jen triviální typ a rozdíl ve výkonu je triviální a kompilátor se dá snadno optimalizovat.

Tip: Programujete v jazyce C nebo v jazyce C++ a otázky se týkají jednoho nebo druhého, nikoli obou.

4
DeadMG

Výkon obou operací je vysoce závislý na základní architektuře. Je třeba zvýšit hodnotu, která je uložena v paměti, což znamená, že v obou případech je limitujícím faktorem von Neumann omezující faktor.

V případě ++ i, musíme

Fetch i from memory 
Increment i
Store i back to memory
Use i

V případě i ++ musíme

Fetch i from memory
Use i
Increment i
Store i back to memory

Operátoři ++ a - sledují jejich původ do instrukční sady PDP-11. PDP-11 mohl provádět automatický dodatečný přírůstek v registru. Mohlo by také provádět automatické předběžné snižování na efektivní adrese obsažené v registru. V obou případech mohl kompilátor využít těchto operací na stroji, pouze pokud byla dotyčná proměnná proměnnou „register“.

2
bit-twiddler

Pokud chcete vědět, jestli je něco pomalé, vyzkoušejte to. Vezměte BigInteger nebo ekvivalent, držte ho v podobné smyčce pomocí obou idiomů, ujistěte se, že se uvnitř smyčky nedostane optimalizace pryč, a čas je oba.

Po přečtení článku se mi to nezdá přesvědčivé, a to ze tří důvodů. Za prvé, kompilátor by měl být schopen optimalizovat kolem vytvoření objektu, který se nikdy nepoužívá. Dva, i++ koncept je idiomatický pro numerický pro smyčky, takže případy, které vidím, že jsou skutečně ovlivněny, jsou omezeny na. Za třetí, poskytují čistě teoretický argument, bez čísel, která by ho podporovala.

Zejména z důvodu č. 1 se domnívám, že když skutečně provedete načasování, budou přímo vedle sebe.

2
jprete