it-swarm.cn

避免使用Postfix增量运算符

我已经读到,出于性能原因(在某些情况下),我应该 避免使用后缀增量运算符

但这不影响代码的可读性吗?在我看来:

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

看起来比:

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

但这可能只是出于习惯。诚然,我没有看到很多使用++i

在这种情况下,性能是否牺牲了可读性?还是我只是盲目,而++ii++更易读?

25
Mateen Ulhaq

事实:

  1. i ++和++ i同样易于阅读。您不喜欢它是因为您不习惯它,但是从本质上讲,您没有什么可以误解的,因此读或写它不再是一件费事的事情。

  2. 至少在某些情况下,后缀运算符的效率较低。

  3. 但是,在99.99%的情况下,这并不重要,因为(a)无论如何它都将作用于简单或原始类型,并且如果要复制大对象,这只是一个问题(b)不会在性能上出现问题代码(c)的关键部分,您可能不知道编译器是否会对其进行优化。

  4. 因此,我建议您使用前缀,除非您特别需要后缀是一种好习惯,因为(a)与其他事物保持精确一致是一个好习惯,并且(b)在蓝月亮中,您打算使用后缀并以错误的方式解决它:如果您总是写出您的意思,那可能性就很小。在性能和优化之间始终要取舍。

您应该使用常识,而不是在需要之前不要进行微优化,但是无论如何都不要低效。通常,这意味着:首先,排除即使在非时间紧迫的代码中效率也不可接受的任何代码构造(通常代表基本概念错误的代码构造,例如无缘无故地按值传递500MB对象);其次,在其他所有编写代码的方式中,选择最清晰的方式。

但是,在这里,我相信答案很简单:我相信写前缀,除非您特别需要后缀(a)非常清晰,并且(b)更可能更有效率,所以默认情况下,您应该始终编写前缀,但是如果您忘记了,不必担心。

六个月前,我和您一样认为i ++更自然,但这纯粹是您的习惯。

编辑1:我通常对此事信任的“更有效的C++”中的斯科特·迈耶斯说,您通常应该避免在用户定义的类型上使用后缀运算符(因为后缀增量功能的唯一明智的实现是使复制对象,调用前缀递增函数执行递增操作,然后返回副本,但是复制操作可能会很昂贵。

因此,我们不知道关于(a)今天是否正确,(b)它是否也适用于内在类型(c)是否应在其上使用“ ++”,没有任何通用规则。没有比轻量级迭代器类更重要的东西了。但是,由于上述所有原因,请不要执行我之前所说的事情。

编辑2:这是指一般惯例。如果您认为在某些特定情况下确实很重要,则应该对其进行概要分析并查看。概要分析既简单又便宜,并且可行。从首要原则中推论出需要优化的内容是困难且昂贵的,而且行不通。

58
Jack V.

始终首先为程序员编码,然后为计算机编码。

如果存在性能差异,则在编译器对代码进行专家检查之后,[〜#〜]和[〜#〜]您可以对其进行测量[〜#〜]和[〜#〜]很重要-然后您可以更改它。

62
Martin Beckett

GCC为两个循环产生相同的机器代码。

C代码

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;
}

汇编代码(我的意见)

    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

97%的时间不用担心性能。过早的优化是万恶之源。

-唐纳德​​·努斯

既然这已经不合我们了,让我们明智地选择:

  • ++i前缀递增,递增当前值并产生结果
  • i++后缀增量,复制值,递增当前值,产生副本

除非需要复制旧值,否则使用后缀增量是完成任务的一种round回方式。

Inaccuracy来自懒惰,请始终使用最直接表达您意图的构造,与将来的维​​护者可能会误解您的原始意图的机会相比,这种机会更少。

即使在这里(真的)很小,但有时我还是对阅读代码感到困惑:我真的想知道意图和实际表达是否重合,当然,几个月后,它们(或我)也不记得了.

因此,它是否对您来说都没关系。拥抱[〜#〜] kiss [〜#〜]。再过几个月,您将避免使用旧的做法。

12
Matthieu M.

在C++中,如果涉及运算符重载,则could会产生实质性的性能差异,尤其是如果您正在编写模板代码并且不知道可能传递什么迭代器时。任何迭代器X背后的逻辑可能既实质又重要-即速度慢且无法被编译器优化。

但是,在C语言中情况并非如此,在C语言中,它只会是一个琐碎的类型,并且性能差异是琐碎的,并且编译器可以轻松地进行优化。

提示:您使用C或C++进行编程,并且问题与一个或另一个相关,而不是两者都相关。

4
DeadMG

每种操作的性能高度依赖于基础架构。必须增加存储在内存中的值,这意味着在两种情况下冯·诺依曼瓶颈都是限制因素。

对于++ i,我们必须

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

对于i ++,我们必须

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

++和-运算符将其原点追溯到PDP-11指令集。 PDP-11可以对寄存器执行自动后递增。它还可以对寄存器中包含的有效地址执行自动递减。在这两种情况下,如果所讨论的变量是“寄存器”变量,则编译器只能利用这些机器级操作。

2
bit-twiddler

如果您想知道是否有些慢,请进行测试。取一个BigInteger或等效值,使用这两个习惯用法将其粘贴在类似的for循环中,确保循环内部不会被优化,并对两者进行计时。

阅读了这篇文章后,我认为它没有说服力,原因有三点。第一,编译器应该能够围绕从未使用过的对象的创建进行优化。二,i++概念是数字用于循环的惯用语,因此我可以看到实际受影响的情况仅限于第三,它们提供了纯理论上的论据,没有数字可以支持它。

特别是基于原因1,我的猜测是,当您实际进行计时时,它们将彼此相邻。

2
jprete