为什么++我可能与i + = 1表现不同

Sin*_*all 2 c c++ optimization performance

显然是在阅读了旧标题之后

为什么is ++i fster than i+=1甚至存在这样的问题

人们没有费心去彻底阅读这个问题.

问题在于人们提出这个问题的理由!这是关于为什么编译器会在++i和之间产生差异的原因i+=1,并且是否有任何可能的情况才有意义.虽然我很欣赏你所有的诙谐和深刻的评论,但我的问题并非如此.


好吧,好吧,让我试着用另一种方式提出问题,我希望我的英语足够好,这次我可以表达自己而不会被误解,所以请阅读.让我们说有人在一本10年前的书中读到这篇文章:

使用++ i over i = i + 1可以获得性能优势.

我并不热衷于这个特定的例子,而是更多或更少地谈论.

显然,当作者写这本书时,对他来说是有道理的,他不仅仅是弥补了这一点.我们知道现代编译器不关心你是否使用++i,i+=1或者i = i + 1代码将被优化,我们将具有相同的asm输出.

这似乎很合乎逻辑:如果两个操作执行相同的操作并且具有相同的结果,则没有理由编译++i成一个东西,而是i+=1另一个东西.

但是自从这本书作者写下来之后,他已经看到了的不同之处!这意味着某些编译器实际上会为这两行产生不同的输出.这意味着,由编译器的家伙有一些原因,治疗++ii+=1不同.我的问题是他们为什么会这样做?

是不是因为那些日子很难/不可能使编译器足够先进以进行这样的优化?或者,也许在一些非常具体的平台/硬件/在某些特殊情况下它实际上是有意义的,使之间的差异++i,并i+=1和其他同类的东西?或者它可能取决于变量类型?或者编译器开发者只是懒惰?

Ste*_*sop 12

想象一下非优化编译器.它真的不关心是否++i相同i+=1,它只是发出它能想到的第一件事.它知道CPU有一个加法指令,它知道CPU有一个递增整数的指令.所以假设i有类型int,那么++i它发出类似的东西:

inc <wherever_i_is>
Run Code Online (Sandbox Code Playgroud)

因为i+=1,它发出类似于:

load the constant 1 into a register
add <wherever_i_is> to that register
store that register to <wherever_i_is>
Run Code Online (Sandbox Code Playgroud)

为了确定后面的代码"应该"与前者相同,编译器必须注意到添加的常量是1,而不是2或1007.这需要编译器中的专用代码,标准不会需要它,而不是每个编译器都一直这样做.

所以你的问题相当于"为什么编译器会比我更笨,因为我发现了这种等价而它没有?".答案是,现代编译器在很多时候都比你聪明,但并非总是如此,并非总是如此.

自书作者撰写以来,他已经看到了差异

不必要.如果你看到关于什么是"更快"的声明,有时候这本书的作者比你和编译器都要笨重.有时他很聪明,但他巧妙地在不再适用的条件下形成了他的经验法则.有时他推测编译器的存在就像我上面描述的那样愚蠢,而没有真正检查你实际使用的编译器是否真的那么愚蠢.就像我刚刚做的那样;-)

Btw,10年前对于一个优化启用的合适编译器来说太近了,不能进行这种特殊的优化.确切的时间尺度可能与你的问题无关,但如果一位作者写道并且他们的借口是"那可以追溯到2002年",那么我个人不会接受它.声明不再比现在更正确.如果他们说1992年那么好,我个人不知道那时编译器是什么样的,我不能反驳他们.如果他们说1982年那么我仍然会怀疑(毕竟,C++已经被发明了.它的大部分设计依赖于优化编译器,以避免在运行时浪费大量的工作,但我会批准这个事实的最大用户是模板容器/算法,它在1982年不存在).如果他们说1972年,我可能只是相信他们.肯定有一段时间C编译器是美化装配工.


Ale*_*nze 9

在C中,i++一般不等同于i=i+1因为两者产生不同的表达值.++i相当于i=i+1因为它们产生相同的表达值.

i不使用上述三个表达式中的任何一个的值的情况下,三者是相同的.如果它是一个好的编译器,它可以优化掉生成的未使用的临时变量i++.

这个临时变量栩栩如生,因为它i++决定了以下两件事:

  1. i表达式返回原始值i++
  2. i 增加1

如果你先取原始值 i然后递增i,那么,原始(现在旧的)值必须i存在于某处(存储器或寄存器,无关紧要),因为它不能存在于现在递增的变量中i.那是你的临时变量.

如果,OTOH,你首先递增i1,然后再次,你必须在某处(在寄存器或存储器中)创建一个等于i-1撤消增量的值,因此可以获得旧的(pred-incremented)值作为结果表达i++.

随着++ii=i+1事情要简单得多.这些表达式要求两件事:

  1. i 得到增加
  2. i返回新值

在这里,i首先递增然后取其值是很自然的.您不必拥有ii+1(i-1i)旧的和新的一对值.新的就是我们所需要的.

现在,当编译器不是很擅长优化时,就会出现旧书和老人.从那里可以得到一个i++可能比它慢的想法++i.在实践中观察到差异,而不是弥补.这是真实的,有些人可能认为今天仍然如此.

人们还可以尝试分析两个(三个)递增表达式之间的差异,并看到确实可能需要做一些额外的操作,并在临时变量的情况下使用额外的存储器单元i++.此时,此人可能无法确定何时不需要此临时或如何检测是否有必要.对于上述差异的问题,这是另一种可能性.

当然,人们一直都喜欢拖钓.:)

至于编译器开发人员懒惰......我不认为他们是.这就是原因.

回到过去,计算机比现在慢得多,而且它们的RAM也少得多.

即使这样,也可以编写一个不错的优化编译器

问题是优化的额外代码使编译器明显变大和变慢.如果它更大,可以运行更少的计算机,更少的程序员可以使用它来编译代码.如果它比其他编译器慢,人们会更喜欢其他编译器,因为人们讨厌坐着等待.

一个很好的例子:我.我在90年代中期确实可以访问Borland的Turbo C/C++.但直到90年代末,0年代早期,我才考虑学习和使用C. 原因?Borland的C/C++比他们的Pascal慢得多,我的PC也不是很好.等待代码编译是痛苦的.这就是它的样子.我第一次掌握了Pascal,后来又回到了C和C++.

因此,更聪明,更大,更慢的编译器会花费编译器用户的金钱和时间.至少在主动开发期间,即使最终产品使用不同的编译器进行编译,这仍然是一个非常重要的产品阶段.

你不应该忘记用那些日子的基本工具开发和管理一大块编译器代码也不是很有趣.现在只有你可以拥有一个漂亮的IDE(而不是一个!),其中包含一个调试器,语法高亮,自动完成和所有,源代码管理,简单的文件比较,Internet/StackOverflow等等......我们可以现在有多个20+"显示器连接到PC!现在我们说的是生产力!:)

真的,我们今天拥有很棒的工具和设备.20年,30年,40年前人们只能想象或预测它们,但尚未使用.

事情变得更加艰难.而且,虽然我不打算在这里发表声明,但当我发现当时编程没有像现在那样商品化时,我不会感到惊讶,那里有比现在更好的优秀程序员.当然,这不是绝对数字,而是相对而言.

所以,我怀疑编译器的人是懒惰的.

在网上查找所谓的内容Small C.它是一个通用术语,用于实现C语言最重要特性的功能简化和功能减少的C编译器.你会发现一些实现Ron Cain,James Hendrix(80年代早期)和其他的衍生物(例如RatC/Lancaster实现的Bob BerryBrian Meekings).

如果您查看其中任何一个的代码Small C's,您会发现最小代码大小约为50 + KB且2 + KLOC,而且这只是从C到汇编代码的翻译器!有人在某个时候需要用汇编程序组装它.

我无法想象在8位家用计算机上安装这样的项目,例如ZX-Spectrum(我小时候),最大可能有48KB RAM,CPU运行在~3MHz ,所有存储都在录音机上,数据传输速率为10KB/min,屏幕为32x24,甚至不是80x25.

而80年代早期的所有Small C代码,几乎不适合计算机的内存,并没有优化任何东西!