如果未使用结果值,i++
和++i
之间是否存在性能差异?
答案 0 :(得分:359)
执行摘要:否。
i++
可能比++i
慢,因为i
的旧值
可能需要保存以供以后使用,但在实践中都是现代的
编译器将优化它。
我们可以通过查看此函数的代码来证明这一点,
同时使用++i
和i++
。
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
除++i
和i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
我们将编译它们,并获得生成的汇编程序:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
我们可以看到生成的对象和汇编程序文件都是相同的。
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
答案 1 :(得分:98)
Andrew Koenig来自Efficiency versus intent:
首先,至少在涉及整数变量的情况下,
++i
比i++
更有效是很明显的。
并且:
因此,人们应该问的问题不是这两个操作中的哪一个更快,而是这两个操作中的哪一个更准确地表达了您要完成的任务。我提交的是,如果你没有使用表达式的值,就没有理由使用
i++
而不是++i
,因为没有理由复制变量的值,增加变量,然后扔掉副本。
因此,如果未使用结果值,我将使用++i
。但不是因为它更有效:因为它正确地表明了我的意图。
答案 2 :(得分:43)
更好的答案是++i
有时会更快,但从不慢。
每个人似乎都认为i
是常规的内置类型,例如int
。在这种情况下,没有可衡量的差异。
但是,如果i
是复杂类型,那么您可能会发现可衡量的差异。对于i++
,您必须在增加课程之前制作课程副本。根据副本中涉及的内容,它确实可能会更慢,因为++it
只能返回最终值。
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
另一个区别是,使用++i
,您可以选择返回引用而不是值。同样,根据制作对象副本所涉及的内容,这可能会更慢。
可能发生这种情况的真实例子是使用迭代器。复制迭代器不一定是你应用程序中的瓶颈,但是习惯使用++i
代替i++
而不影响结果的习惯仍然是好习惯。
答案 3 :(得分:16)
从Scott Meyers那里得到一片叶子,More Effective c++ 第6项:区分增量和减量操作的前缀和后缀形式。
对于对象,前缀版本总是比postfix更受欢迎,特别是在迭代器方面。
如果你看一下运营商的呼叫模式,原因就在于此。
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
看一下这个例子,很容易看出前缀运算符总是比postfix更有效。因为在使用后缀时需要一个临时对象。
这就是为什么当你看到使用迭代器的例子时,他们总是使用前缀版本。
但正如你指出的那样,由于可以进行编译器优化,实际上没有区别。
答案 4 :(得分:16)
如果你担心微观优化,这是另外一个观察。在给定的情况下,递减循环“可能”比递增循环(取决于指令集架构,例如ARM)更有效:
for (i = 0; i < 100; i++)
在每个循环中,您将分别获得一条指令:
1
添加到i
。 i
是否小于100
。i
小于100
,则为条件分支。而递减循环:
for (i = 100; i != 0; i--)
循环将包含以下各项的说明:
i
,设置CPU寄存器状态标志。Z==0
)。当然,只有在递减到零时才有效!
从ARM System Developer's Guide中记得。
答案 5 :(得分:14)
简答:
在速度方面,i++
和++i
之间从来没有任何区别。一个好的编译器不应该在这两种情况下生成不同的代码。
答案很长:
其他每个答案都没有提及,++i
与i++
之间的区别仅在其找到的表达式中有意义。
在for(i=0; i<n; i++)
的情况下,i++
在其自己的表达式中是唯一的:在i++
之前有一个序列点,后面有一个序列点。因此,生成的唯一机器代码是i
&#34;增加1
。并且明确定义了如何根据程序的其余部分对其进行排序。因此,如果您将其更改为前缀++
,那么最重要的是,您仍然可以获得机器代码&#34; i
增加1
& #34;
++i
和i++
之间的差异仅在array[i++] = x;
与array[++i] = x;
等表达式中有关。有些人可能会争辩说并且在这种操作中后缀会更慢,因为i
所在的寄存器必须稍后重新加载。但请注意,编译器可以自由地以任何方式订购您的指令,只要它不会破坏抽象机器的行为即可。正如C标准所称。
因此,您可以假设array[i++] = x;
被转换为机器代码:
i
在寄存器A中的存储价值。i
在寄存器A中的存储值//效率低,因为这里有额外说明,我们已经这样做了一次。i
。编译器也可以更有效地生成代码,例如:
i
在寄存器A中的存储价值。i
。仅仅因为你作为一名C程序员受过训练,认为最后发生了后缀++
,机器代码就不必以这种方式进行排序。
因此C中的前缀和后缀++
之间没有区别。现在你作为一个C程序员应该是多变的,在某些情况下不一致使用前缀的人和其他情况下的后缀,没有任何理由为什么。这表明他们不确定C是如何工作的,或者他们对语言的认识不正确。这总是一个不好的迹象,它反过来表明他们在他们的计划中做出其他可疑的决定,基于迷信或宗教教条&#34;。
&#34;前缀++
总是更快&#34;确实是一个这样的错误教条,在未来的C程序员中很常见。
答案 6 :(得分:11)
请不要让“哪一个更快”的问题成为决定使用的因素。你可能永远不会那么在意,而且程序员的阅读时间比机器时间要贵得多。
使用对阅读代码最有意义的人。
答案 7 :(得分:9)
首先:i++
和++i
之间的区别在C中是可以忽略的。
详情。
++i
更快在C ++中,如果++i
是某种具有重载增量运算符的对象,i
会更有效。
为什么?
在++i
中,对象首先递增,然后可以作为const引用传递给任何其他函数。如果表达式为foo(i++)
,则无法执行此操作,因为现在需要在调用foo()
之前完成增量,但旧值需要传递给foo()
。因此,编译器在执行原始增量运算符之前必须复制i
。额外的构造函数/析构函数调用是不好的部分。
如上所述,这不适用于基本类型。
i++
可能更快如果不需要调用构造函数/析构函数(C中总是如此),++i
和i++
应该同样快,对吧?不。它们几乎同样快速,但可能存在细微的差异,大多数其他答案者都错了。
i++
怎样才能更快?
关键是数据依赖性。如果需要从内存加载该值,则需要对其进行两次后续操作,递增并使用它。使用++i
时,需要在之前完成增量可以使用该值。对于i++
,使用不依赖于增量,并且CPU可以并行执行使用操作 到增量操作。差异最多只有一个CPU周期,所以它确实是可以忽略不计的,但它确实存在。而这是许多人所期望的另一种方式。
答案 8 :(得分:7)
@马克 即使允许编译器优化掉(基于堆栈)变量的临时副本,gcc(在最近的版本中)也是如此, 并不意味着所有编译器都会这样做。
我刚刚使用我们当前项目中使用的编译器对其进行了测试,其中3个未对其进行优化。
永远不要假设编译器正确,特别是如果可能更快,但从不慢的代码更容易阅读。
如果你的代码中没有一个非常愚蠢的运算符实现:
我更喜欢++ i而不是i ++。
答案 9 :(得分:5)
在C中,如果结果未使用,编译器通常可以将它们优化为相同。
但是,在C ++中如果使用提供自己的++运算符的其他类型,前缀版本可能比后缀版本更快。因此,如果您不需要后缀语义,最好使用前缀运算符。
答案 10 :(得分:4)
我可以想到postfix比前缀增量慢的情况:
想象一下,寄存器A
的处理器用作累加器,它是许多指令中使用的唯一寄存器(一些小型微控制器实际上就是这样)。
现在想象下面的程序及其翻译成假设的程序集:
前缀增量:
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]
后缀增量:
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]
注意如何强制重新加载b
的值。使用前缀增量,编译器可以只增加该值并继续使用它,可能避免重新加载它,因为在增量之后所需的值已经在寄存器中。但是,使用postfix增量时,编译器必须处理两个值,一个是旧值,另一个是递增值,如上所示,这会导致一次内存访问。
当然,如果未使用增量的值,例如单个i++;
语句,编译器可以(并且确实)生成增量指令,而不管后缀或前缀使用情况。
作为旁注,我想提一下,b++
的表达式不能简单地转换为++b
的表达式而无需任何额外的努力(例如添加{ {1}})。因此,如果它们是某些表达式的一部分,则比较它们并不是真正有效的。通常,如果您在表达式中使用- 1
,则无法使用b++
,因此即使++b
可能更有效,也只会出错。当然,例外情况是表达式要求它(例如++b
可以更改为a = b++ + 1;
)。
答案 11 :(得分:3)
我已经阅读了这里的大多数答案和许多评论,但没有看到我能想到的{strong> one 实例的任何引用i++
比++i
更有效(也许令人惊讶的是--i
比i--
更有效)。那是针对DEC PDP-11的C编译器!
PDP-11的组装说明用于寄存器的递减和递增,但反之则不行。该指令允许将任何“通用”寄存器用作堆栈指针。因此,如果您使用*(i++)
之类的东西,则可以将其编译为单个汇编指令,而*(++i)
则不能。
这显然是一个非常深奥的例子,但是它确实提供了例外,即后增量效率更高(或者我应该说是,因为对PDP-11 C的需求不多这些天编码)。
答案 12 :(得分:2)
我总是喜欢预增量,但是......
我想指出,即使在调用operator ++函数的情况下,如果函数内联,编译器也能够优化临时函数。由于operator ++通常很短并且经常在标题中实现,因此很可能会内联。
因此,出于实际目的,两种形式的表现之间可能没有太大差异。但是,我总是喜欢预增量,因为直接表达我想说的内容似乎更好,而不是依靠优化器来解决它。
此外,减少optmizer可能意味着编译器运行得更快。
答案 13 :(得分:0)
我的C有点生疏,所以我提前道歉。 Speedwise,我可以理解结果。但是,我很困惑两个文件是如何出现在同一个MD5哈希中的。也许for循环运行相同,但是下面两行代码不会生成不同的汇编吗?
myArray[i++] = "hello";
VS
myArray[++i] = "hello";
第一个将值写入数组,然后递增i。然后第二个增量i写入数组。我不是汇编专家,但我只是看不出这两行代码如何生成相同的可执行文件。
只是我的两分钱。