简体   繁体   English

在C中,i ++和++ i之间是否存在性能差异?

[英]Is there a performance difference between i++ and ++i in C?

如果不使用结果值, i++++i之间会有性能差异吗?

Executive summary: No. 内容提要:否。

i++ could potentially be slower than ++i , since the old value of i might need to be saved for later use, but in practice all modern compilers will optimize this away. i++可能比++i慢,因为可能需要保存i的旧值以供以后使用,但实际上所有现代编译器都会对其进行优化。

We can demonstrate this by looking at the code for this function, both with ++i and i++ . 我们可以通过使用++ii++查看此函数的代码来证明这一点。

$ cat i++.c
extern void g(int i);
void f()
{
    int i;

    for (i = 0; i < 100; i++)
        g(i);

}

The files are the same, except for ++i and i++ : 这些文件是相同的,除了++ii++

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

We'll compile them, and also get the generated assembler: 我们将对其进行编译,并获得生成的汇编器:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

And we can see that both the generated object and assembler files are the same. 我们可以看到生成的对象文件和汇编文件都是相同的。

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

From Efficiency versus intent by Andrew Koenig : 效率与意图 (作者Andrew Koenig):

First, it is far from obvious that ++i is more efficient than i++ , at least where integer variables are concerned. 首先,至少在涉及整数变量的情况下, ++ii++效率更高还很明显。

And : 和:

So the question one should be asking is not which of these two operations is faster, it is which of these two operations expresses more accurately what you are trying to accomplish. 因此,一个人应该问的问题不是这两个操作中的哪个更快,而是这两个操作中的哪个更准确地表达了您要完成的任务。 I submit that if you are not using the value of the expression, there is never a reason to use i++ instead of ++i , because there is never a reason to copy the value of a variable, increment the variable, and then throw the copy away. 我认为,如果您不使用表达式的值,则永远没有理由使用i++而不是++i ,因为永远没有理由复制变量的值,增加变量然后抛出复制。

So, if the resulting value is not used, I would use ++i . 因此,如果不使用结果值,我将使用++i But not because it is more efficient: because it correctly states my intent. 但这不是因为它更有效:因为它正确地表达了我的意图。

A better answer is that ++i will sometimes be faster but never slower. 更好的答案是++i有时会更快,但永远不会慢。

Everyone seems to be assuming that i is a regular built-in type such as int . 每个人似乎都假设i是一个常规的内置类型,例如int In this case there will be no measurable difference. 在这种情况下,没有可测量的差异。

However if i is complex type then you may well find a measurable difference. 但是,如果i是复杂类型,那么您可能会发现可测量的差异。 For i++ you must make a copy of your class before incrementing it. 对于i++您必须在增加类之前对其进行复制。 Depending on what's involved in a copy it could indeed be slower since with ++it you can just return the final value. 取决于副本所涉及的内容,它的确确实会变慢,因为使用++it可以只返回最终值。

Foo Foo::operator++()
{
  Foo oldFoo = *this; // copy existing value - could be slow
  // yadda yadda, do increment
  return oldFoo;
}

Another difference is that with ++i you have the option of returning a reference instead of a value. 另一个区别是,使用++i您可以选择返回引用而不是值。 Again, depending on what's involved in making a copy of your object this could be slower. 同样,根据制作对象副本所涉及的内容,这可能会更慢。

A real-world example of where this can occur would be the use of iterators. 迭代器的使用是一个现实的例子。 Copying an iterator is unlikely to be a bottle-neck in your application, but it's still good practice to get into the habit of using ++i instead of i++ where the outcome is not affected. 复制迭代器在您的应用程序中不太可能成为瓶颈,但是在不影响结果的情况下,养成使用++i而不是i++的习惯仍然是一种好习惯。

Taking a leaf from Scott Meyers, More Effective c++ Item 6: Distinguish between prefix and postfix forms of increment and decrement operations . 摘自Scott Meyers的《 更有效的c ++ 项目6》:区分增量和减量运算的前缀和后缀形式

The prefix version is always preferred over the postfix in regards to objects, especially in regards to iterators. 对于对象,尤其是对于迭代器,始终优先使用前缀版本而不是后缀。

The reason for this if you look at the call pattern of the operators. 如果查看运营商的呼叫方式,则其原因是。

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

Looking at this example it is easy to see how the prefix operator will always be more efficient than the postfix. 看这个例子,很容易看出前缀运算符将总是比后缀更有效率。 Because of the need for a temporary object in the use of the postfix. 由于在使用postfix时需要一个临时对象。

This is why when you see examples using iterators they always use the prefix version. 这就是为什么当您看到使用迭代器的示例时,它们始终使用前缀版本的原因。

But as you point out for int's there is effectively no difference because of compiler optimisation that can take place. 但是正如您为int指出的那样,由于可以进行编译器优化,因此实际上没有区别。

Short answer: 简短答案:

There is never any difference between i++ and ++i in terms of speed. 在速度方面, i++++i从来没有任何区别。 A good compiler should not generate different code in the two cases. 好的编译器在两种情况下不应生成不同的代码。

Long answer: 长答案:

What every other answer fails to mention is that the difference between ++i versus i++ only makes sense within the expression it is found. 其他所有答案都没有提到, ++ii++之间的区别仅在找到的表达式中才有意义。

In the case of for(i=0; i<n; i++) , the i++ is alone in its own expression: there is a sequence point before the i++ and there is one after it. for(i=0; i<n; i++)i++本身就是它自己的表达式: i++之前有一个序列点,而i++之后有一个点。 Thus the only machine code generated is "increase i by 1 " and it is well-defined how this is sequenced in relation to the rest of the program. 因此,生成的唯一机器代码是“将i递增1 ”,并且很好地定义了如何相对于程序的其余部分对其进行排序。 So if you would change it to prefix ++ , it wouldn't matter in the slightest, you would still just get the machine code "increase i by 1 ". 因此,如果将其更改为前缀++ ,则丝毫没有关系,您仍将获得机器代码“将i递增1 ”。

The differences between ++i and i++ only matters in expressions such as array[i++] = x; ++ii++之间的区别仅在诸如array[i++] = x;表达式中array[i++] = x; versus array[++i] = x; vs array[++i] = x; . Some may argue and say that the postfix will be slower in such operations because the register where i resides have to be reloaded later. 有人可能会说,后缀在这种操作中会变慢,因为i居住的寄存器必须稍后重新加载。 But then note that the compiler is free to order your instructions in any way it pleases, as long as it doesn't "break the behavior of the abstract machine" as the C standard calls it. 但是请注意,只要编译器不按C标准所称的那样“破坏抽象机的行为”,它就可以按照自己喜欢的任何方式自由地对其指令进行排序。

So while you may assume that array[i++] = x; 因此,尽管您可以假设array[i++] = x; gets translated to machine code as: 转换为机器代码为:

  • Store value of i in register A. i值存储在寄存器A中。
  • Store address of array in register B. 将数组的地址存储在寄存器B中。
  • Add A and B, store results in A. 添加A和B,将结果存储在A中。
  • At this new address represented by A, store the value of x. 在以A表示的这个新地址处,存储x的值。
  • Store value of i in register A // inefficient because extra instruction here, we already did this once. i值存储在寄存器A //中效率低下,因为这里有额外的指令,我们已经执行了一次。
  • Increment register A. 增量寄存器A。
  • Store register A in i . 将寄存器A存储在i

the compiler might as well produce the code more efficiently, such as: 编译器也可能更有效地产生代码,例如:

  • Store value of i in register A. i值存储在寄存器A中。
  • Store address of array in register B. 将数组的地址存储在寄存器B中。
  • Add A and B, store results in B. 添加A和B,将结果存储在B中。
  • Increment register A. 增量寄存器A。
  • Store register A in i . 将寄存器A存储在i
  • ... // rest of the code. ... //其余代码。

Just because you as a C programmer is trained to think that the postfix ++ happens at the end, the machine code doesn't have to be ordered in that way. 仅仅因为您作为C程序员已被训练为认为后缀++会在末尾出现,所以不必以这种方式对机器代码进行排序。

So there is no difference between prefix and postfix ++ in C. Now what you as a C programmer should be vary of, is people who inconsistently use prefix in some cases and postfix in other cases, without any rationale why. 因此,在C ++中,前缀和后缀++之间没有区别。作为C程序员,您现在应该有所不同的是,在某些情况下不一致使用前缀的人和在其他情况下不一致使用后缀的人,没有任何理由。 This suggests that they are uncertain about how C works or that they have incorrect knowledge of the language. 这表明他们不确定C的工作方式或对语言的了解不正确。 This is always a bad sign, it does in turn suggest that they are making other questionable decisions in their program, based on superstition or "religious dogmas". 这总是一个不好的信号,它确实表明他们正在基于迷信或“宗教教条”在他们的程序中做出其他可疑的决定。

"Prefix ++ is always faster" is indeed one such false dogma that is common among would-be C programmers. 实际上,“ Prefix ++总是更快”是一种可能在C语言程序员中普遍存在的错误教条。

Here's an additional observation if you're worried about micro optimisation. 如果您担心微优化,这是另外一个观察结果。 Decrementing loops can 'possibly' be more efficient than incrementing loops (depending on instruction set architecture eg ARM), given: 给定以下条件,减少循环可能比增加循环更有效率(取决于指令集架构,例如ARM):

for (i = 0; i < 100; i++)

On each loop you you will have one instruction each for: 在每个循环中,您将分别具有一条指令:

  1. Adding 1 to i . 1加到i
  2. Compare whether i is less than a 100 . 比较i是否小于100
  3. A conditional branch if i is less than a 100 . 如果i小于100则为条件分支。

Whereas a decrementing loop: 而递减循环:

for (i = 100; i != 0; i--)

The loop will have an instruction for each of: 该循环将为每个指令提供一条指令:

  1. Decrement i , setting the CPU register status flag. i ,设置CPU寄存器状态标志。
  2. A conditional branch depending on CPU register status ( Z==0 ). 根据CPU寄存器状态( Z==0 )的条件分支。

Of course this works only when decrementing to zero! 当然,这仅在减为零时有效!

Remembered from the ARM System Developer's Guide. 从《 ARM系统开发人员指南》中记住。

Please don't let the question of "which one is faster" be the deciding factor of which to use. 请不要让“哪个更快”的问题成为使用哪个的决定因素。 Chances are you're never going to care that much, and besides, programmer reading time is far more expensive than machine time. 您永远都不会在乎那么多,此外,程序员的阅读时间比机器学习的时间要昂贵得多。

Use whichever makes most sense to the human reading the code. 使用对人类阅读代码最有意义的方法。

First of all: The difference between i++ and ++i is neglegible in C. 首先:在C中, i++++i之间的区别可以忽略不计。


To the details. 到细节。

1. The well known C++ issue: ++i is faster 1.众所周知的C ++问题: ++i更快

In C++, ++i is more efficient iff i is some kind of an object with an overloaded increment operator. 在C ++中,如果i是某种带有重载增量运算符的对象,则++i效率更高。

Why? 为什么?
In ++i , the object is first incremented, and can subsequently passed as a const reference to any other function. ++i ,对象首先增加,然后可以作为const引用传递给任何其他函数。 This is not possible if the expression is foo(i++) because now the increment needs to be done before foo() is called, but the old value needs to be passed to foo() . 如果表达式是foo(i++)这是不可能的,因为现在需要在调用foo()之前完成增量,但是需要将旧值传递给foo() Consequently, the compiler is forced to make a copy of i before it executes the increment operator on the original. 因此,在对原始文件执行增量运算符之前,编译器被迫复制i The additional constructor/destructor calls are the bad part. 额外的构造函数/析构函数调用是最糟糕的部分。

As noted above, this does not apply to fundamental types. 如上所述,这不适用于基本类型。

2. The little known fact: i++ may be faster 2.鲜为人知的事实: i++ 可能会更快

If no constructor/destructor needs to be called, which is always the case in C, ++i and i++ should be equally fast, right? 如果不需要调用构造函数/析构函数,这在C ++i总是如此, ++ii++应该一样快,对吗? No. They are virtually equally fast, but there may be small differences, which most other answerers got the wrong way around. 不。它们的速度几乎一样快,但可能会有一些细微的差异,大多数其他应答者都采用了错误的方法。

How can i++ be faster? 如何能i++更快?
The point is data dependencies. 关键是数据依赖性。 If the value needs to be loaded from memory, two subsequent operations need to be done with it, incrementing it, and using it. 如果需要从内存中加载该值,则需要对其进行两个后续操作,使其递增并使用它。 With ++i , the incrementation needs to be done before the value can be used. 对于++i ,需要先进行递增, 然后才能使用该值。 With i++ , the use does not depend on the increment, and the CPU may perform the use operation in parallel to the increment operation. 使用i++ ,使用不取决于增量,CPU可以与增量操作并行执行使用操作。 The difference is at most one CPU cycle, so it is really neglegible, but it is there. 区别最多是一个CPU周期,因此它确实可以忽略不计,但是确实存在。 And it is the other way round then many would expect. 这是许多人期望的另一种方式。

@Mark Even though the compiler is allowed to optimize away the (stack based) temporary copy of the variable and gcc (in recent versions) is doing so, doesn't mean all compilers will always do so. @Mark尽管允许编译器优化变量的(基于堆栈的)临时副本,而gcc(在最新版本中)正在这样做,但这并不意味着所有编译器都会一直这样做。

I just tested it with the compilers we use in our current project and 3 out of 4 do not optimize it. 我只是用我们当前项目中使用的编译器对其进行了测试,但四分之三并未对其进行优化。

Never assume the compiler gets it right, especially if the possibly faster, but never slower code is as easy to read. 永远不要假设编译器正确无误,尤其是在可能更快但永远不会慢的代码易于阅读的情况下。

If you don't have a really stupid implementation of one of the operators in your code: 如果您的代码中没有真正愚蠢的操作符实现:

Alwas prefer ++i over i++. 比起i ++,Alwas更喜欢++ i。

In C, the compiler can generally optimize them to be the same if the result is unused. 在C语言中,如果未使用结果,则编译器通常可以将它们优化为相同。

However, in C++ if using other types that provide their own ++ operators, the prefix version is likely to be faster than the postfix version. 但是,在C ++中,如果使用其他提供自己的++运算符的类型,则前缀版本可能比后缀版本快。 So, if you don't need the postfix semantics, it is better to use the prefix operator. 因此,如果不需要后缀语义,最好使用前缀运算符。

I can think of a situation where postfix is slower than prefix increment: 我可以想到后缀比前缀增量慢的情况:

Imagine a processor with register A is used as accumulator and it's the only register used in many instructions (some small microcontrollers are actually like this). 想象一下,将具有寄存器A的处理器用作累加器,并且它是许多指令中使用的唯一寄存器(某些小型微控制器实际上就是这样)。

Now imagine the following program and their translation into a hypothetical assembly: 现在,假设以下程序及其转换为假设的程序集:

Prefix increment: 前缀增量:

a = ++b + c;

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

Postfix increment: 后缀增量:

a = b++ + c;

; load b
LD    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

Note how the value of b was forced to be reloaded. 请注意b的值是如何被强制重新加载的。 With prefix increment, the compiler can just increment the value and go ahead with using it, possibly avoid reloading it since the desired value is already in the register after the increment. 使用前缀递增,编译器可以只递增该值并继续使用它,可能避免重新加载它,因为所需的值在递增之后已经存在于寄存器中。 However, with postfix increment, the compiler has to deal with two values, one the old and one the incremented value which as I show above results in one more memory access. 但是,使用后缀增量,编译器必须处理两个值,一个是旧值,另一个是增量值,正如我在上面显示的那样,这将导致更多的内存访问。

Of course, if the value of the increment is not used, such as a single i++; 当然,如果不使用增量值,例如单个i++; statement, the compiler can (and does) simply generate an increment instruction regardless of postfix or prefix usage. 语句,无论后缀或前缀使用情况如何,编译器都可以(并且确实)简单地生成增量指令。


As a side note, I'd like to mention that an expression in which there is a b++ cannot simply be converted to one with ++b without any additional effort (for example by adding a - 1 ). 附带说明一下,我想提到一个存在b++的表达式不能简单地用++b转换为一个表达式而无需任何额外的努力(例如,通过添加- 1 )。 So comparing the two if they are part of some expression is not really valid. 因此,如果将两者作为某个表达式的一部分,则将它们进行比较是不正确的。 Often, where you use b++ inside an expression you cannot use ++b , so even if ++b were potentially more efficient, it would simply be wrong. 通常,在表达式中使用b++情况下,不能使用++b ,因此即使++b可能更有效,也完全是错误的。 Exception is of course if the expression is begging for it (for example a = b++ + 1; which can be changed to a = ++b; ). 如果表达式请求它,当然是例外(例如a = b++ + 1;可以更改为a = ++b; )。

I have been reading through most of the answers here and many of the comments, and I didn't see any reference to the one instance that I could think of where i++ is more efficient than ++i (and perhaps surprisingly --i was more efficient than i-- ). 我已经通过大部分的答案在这里读了许多的意见,我没有看到,我能想到的,其中的一个实例的任何引用i++的效率比++i (也许令人惊讶--i i--更有效率。 That is for C compilers for the DEC PDP-11! 那是针对DEC PDP-11的C编译器!

The PDP-11 had assembly instructions for pre-decrement of a register and post-increment, but not the other way around. PDP-11的组装说明用于寄存器的递减和递增,但并非相反。 The instructions allowed any "general-purpose" register to be used as a stack pointer. 该指令允许将任何“通用”寄存器用作堆栈指针。 So if you used something like *(i++) it could be compiled into a single assembly instruction, while *(++i) could not. 因此,如果使用*(i++)之类的东西,则可以将其编译为单个汇编指令,而*(++i)不能。

This is obviously a very esoteric example, but it does provide the exception where post-increment is more efficient(or I should say was , since there isn't much demand for PDP-11 C code these days). 这显然是一个非常深奥的例子,但是它确实提供了例外,即后增量效率更高(或者我应该说 ,因为这几天对PDP-11 C代码的需求不大)。

I always prefer pre-increment, however ... 我总是喜欢预增量,但是...

I wanted to point out that even in the case of calling the operator++ function, the compiler will be able to optimize away the temporary if the function gets inlined. 我想指出的是,即使在调用operator ++函数的情况下,如果函数被内联,编译器也将能够优化掉临时函数。 Since the operator++ is usually short and often implemented in the header, it is likely to get inlined. 由于operator ++通常很短,并且经常在标头中实现,因此很可能会内联。

So, for practical purposes, there likely isn't much of a difference between the performance of the two forms. 因此,出于实际目的,这两种形式的性能可能没有太大区别。 However, I always prefer pre-increment since it seems better to directly express what I"m trying to say, rather than relying on the optimizer to figure it out. 但是,我总是更喜欢预增量,因为直接表达我想说的似乎更好,而不是依靠优化器来解决。

Also, giving the optmizer less to do likely means the compiler runs faster. 同样,减少优化器的工作量可能意味着编译器运行得更快。

My C is a little rusty, so I apologize in advance. 我的C有点生锈,所以我提前道歉。 Speedwise, I can understand the results. 从速度上看,我可以理解结果。 But, I am confused as to how both files came out to the same MD5 hash. 但是,我对这两个文件如何出现在相同的MD5哈希中感到困惑。 Maybe a for loop runs the same, but wouldn't the following 2 lines of code generate different assembly? 也许for循环运行相同,但是以下两行代码不会生成不同的汇编吗?

myArray[i++] = "hello";

vs

myArray[++i] = "hello";

The first one writes the value to the array, then increments i. 第一个将值写入数组,然后递增i。 The second increments i then writes to the array. 然后,第二个增量i写入数组。 I'm no assembly expert, but I just don't see how the same executable would be generated by these 2 different lines of code. 我不是汇编专家,但我只是看不到这两行不同的代码将如何生成相同的可执行文件。

Just my two cents. 只要我两美分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM