在'for'循环中增加1时,格式化的技术原因是什么?

在整个networking中,代码示例都有for循环,如下所示:

 for(int i = 0; i < 5; i++) 

而我使用以下格式:

 for(int i = 0; i != 5; ++i) 

我这样做是因为我相信它更有效率,但在大多数情况下,这确实很重要吗?

每个人都喜欢他们的微观优化,但是就我所能看到的而言,这并没有什么不同。 我用英特尔处理器g ++编译了这两个版本,没有任何花哨的优化,结果是

for(int i = 0; i <5; i ++):

  movl $0, -12(%ebp) jmp L2 L3: leal -12(%ebp), %eax incl (%eax) L2: cmpl $4, -12(%ebp) jle L3 

for(int i = 0; i!= 5; ++ i)

  movl $0, -12(%ebp) jmp L7 L8: leal -12(%ebp), %eax incl (%eax) L7: cmpl $5, -12(%ebp) jne L8 

我认为“jle”和“jne”应该在大多数架构上转换成同样快速的指令。 所以对于表演你不应该区分这两者。 一般来说,我会同意,第一个是更安全一些,我也认为更普遍。

编辑(2年后):由于这个线程最近得到了很多关注,我想补充一点,这个问题一般很难回答。 哪个版本的代码更有效率是由C-Standard [PDF]明确定义的(对于C ++也可能同样适用于C#)。

5.1.2.3节程序执行

§1本标准中的语义描述描述了优化问题无关的抽象机器的行为。

但是,假设一个现代的编译器会产生同样高效的代码是合理的,我认为在极less数情况下,“循环testing”和“计数expression式”是for循环的瓶颈。

至于味道,我写for(int i = 0; i < 5; ++i)

如果由于某种原因, i跳到50循环,你的版本将永远循环。 i < 5是一个健全的检查。

表格

 for (int i = 0; i < 5; i++) 

惯用的 ,所以对于有经验的C程序员来说更容易阅读。 特别是当用于迭代数组。 你应该尽可能写出惯用的代码,因为它读得更快。

当你在循环中修改i或使用不同于1的增量时,它也是一个更安全的方式。但这是一件小事。 最好仔细devise你的循环,并添加一些断言,以尽早发现错误的假设。

如果增量规则稍微改变,你立即有一个无限循环。 我更喜欢第一个结束条件。

这取决于语言。

C ++文本通常会提出第二种格式,因为它可以与迭代器一起使用,迭代器可以直接比较(!=),但是不能使用大于或小于条件的条件。 另外,预增量可以比后增量快,因为不需要用于比较的variables副本 – 但是优化器可以处理这个。

对于整数,任何forms的作品。 C的常见成语是第一个,而C ++则是第二个。

对于C#和Java的使用,我会foreach循环所有的东西。

在C ++中,还有std :: for_each函数需要使用函子,对于简单的情况可能比这里的任何一个例子都更复杂,Boost FOR_EACH可能看起来像C#foreach,但内部是复杂的。

关于使用++我而不是i ++,它与大多数编译器没有什么不同,但是,当我用作迭代器时,我可以更有效率地使用i ++。

实际上你给了四个排列组合。 给你两个:

 for(int i = 0; i < 5; i++) for(int i = 0; i != 5; ++i) 

我们可以添加:

 for(int i = 0; i < 5; ++i) for(int i = 0; i != 5; i++) 

在具有现代编译器的大多数现代机器上,这些效率完全相同也不足为奇。 可能有可能你有一天会发现自己在一些小型处理器上进行编程,在这种情况下,在相等比较和比较之间存在差异。

根据我们select0和5的原因,在某些情况下,某个具体案例的某个特定的头脑可能会更有意义地思考“不及”或“不等于”,但即使如此,编码器可能不会与另一个。

更抽象地说,这些forms是:

 for(someType i = start; i < end; i++) for(someType i = start; i != end; ++i) for(someType i = start; i < end; ++i) for(someType i = start; i != end; i++) 

这里有一个明显的区别是,在两种情况下, someType必须有一个<的意义,其余的必须有!=的含义。 其中!=定义的types和<不是相当常见的,包括C ++中的相当多的迭代器对象(可能在C#中,与STL迭代器相同的方法是可能的,有时是有用的,但不是惯用的,直接由共同图书馆也不常用,因为有更多的直接支持的对手成语)。 值得注意的是,STL方法是专门devise的,以便在有效的迭代器types集合中包含指针。 如果你习惯使用STL,那么即使应用到整数上,你也会认为!=更具惯用的forms。 就个人而言,非常小的曝光量就足以让我的本能。

另一方面,虽然定义<而不是!=会更罕见,但它适用于我们用不同的增量来replace增量,或者i可以在循环内改变的情况。

所以,双方都有确定的情况,哪一方是唯一的方法。

现在为++ii++ 。 再次用整数和直接调用而不是通过返回结果的函数(而偶然性),实际结果将完全相同。

在一些C风格的语言(那些没有运算符重载)中,整数和指针是唯一的情况。 我们可以人为地发明一个例子,通过一个函数调用增量来改变它的运行方式,编译器很可能仍然会把它们变成同样的东西。

C ++和C#允许我们覆盖它们。 通常,前缀++像一个函数那样工作:

 val = OneMoreThan(val);//whatever OneMoreThan means in the context. //note that we assigned something back to val here. return val; 

而后缀++运作就像一个function:

 SomeType copy = Clone(val); val = OneMoreThan(val); return copy; 

C ++和C#都不能完全匹配上面的内容(我非常故意使我的伪代码匹配),但是在任何一种情况下都可能有一个副本,或者可能有两个副本。 这可能也可能不昂贵。 它可以或不可以避免(在C ++中,我们通常可以通过返回this来避免它的前缀forms,而在后缀中返回void)。 它可能会也可能不会被优化,但在某些情况下,执行++ii++更有效。

更特别的是,使用++i可能会略微提高性能,如果是大类,它甚至可能会相当大,但是,如果禁止某个人在C ++中重写,那么这两者的含义完全不同(这是一个非常糟糕的想法),而不是一般来说这可能是相反的。 因此,养成支持前缀而不是后缀的习惯意味着你可能会在一千次之内获得一次改进,但不会失去,所以这是C ++编程人员经常遇到的习惯。

总之,在你的问题中给出的两种情况完全没有区别,但是也可以有相同的变体。

在阅读了Dijkstra的“编程学科”一书之后,我转向使用!=大约20多年前。 在他的书中,迪克斯特拉(Dijkstra)观察到, 较弱的连续条件导致循环结构中较强的后置条件。

例如,如果我们在循环之后修改构造以暴露i ,则第一个循环的后置条件是i >= 5 ,而第二个循环的后置条件是i == 5更强。 这对于循环不变式,后置条件和最弱前提条件下的正式术语的推理来说更好。

我同意关于可读性的说法 – 让维护人员阅读代码很容易,尽pipe你希望那些能够理解前后增量的人都很重要。

这就是说,我认为我会进行一个简单的testing,并获得一些关于四个循环中哪一个运行速度最快的可靠数据。 我在一台普通的电脑上,用javac 1.7.0编译。

我的程序做了一个for循环,在没有任何东西的情况下迭代了200万次(以免在for循环中花费多长时间来处理有趣的数据)。 它使用上面提出的所有四种types,并且将结果乘以1000次,得到平均值。

实际的代码是:

 public class EfficiencyTest { public static int iterations = 1000; public static long postIncLessThan() { long startTime = 0; long endTime = 0; startTime = System.nanoTime(); for (int i=0; i < 2000000; i++) {} endTime = System.nanoTime(); return endTime - startTime; } public static long postIncNotEqual() { long startTime = 0; long endTime = 0; startTime = System.nanoTime(); for (int i=0; i != 2000000; i++) {} endTime = System.nanoTime(); return endTime - startTime; } public static long preIncLessThan() { long startTime = 0; long endTime = 0; startTime = System.nanoTime(); for (int i=0; i < 2000000; ++i) {} endTime = System.nanoTime(); return endTime - startTime; } public static long preIncNotEqual() { long startTime = 0; long endTime = 0; startTime = System.nanoTime(); for (int i=0; i != 2000000; ++i) {} endTime = System.nanoTime(); return endTime - startTime; } public static void analyseResults(long[] data) { long max = 0; long min = Long.MAX_VALUE; long total = 0; for (int i=0; i<iterations; i++) { max = (max > data[i]) ? max : data[i]; min = (data[i] > min) ? min : data[i]; total += data[i]; } long average = total/iterations; System.out.print("max: " + (max) + "ns, min: " + (min) + "ns"); System.out.println("\tAverage: " + (average) + "ns"); } public static void main(String[] args) { long[] postIncLessThanResults = new long [iterations]; long[] postIncNotEqualResults = new long [iterations]; long[] preIncLessThanResults = new long [iterations]; long[] preIncNotEqualResults = new long [iterations]; for (int i=0; i<iterations; i++) { postIncLessThanResults[i] = postIncLessThan(); postIncNotEqualResults[i] = postIncNotEqual(); preIncLessThanResults[i] = preIncLessThan(); preIncNotEqualResults[i] = preIncNotEqual(); } System.out.println("Post increment, less than test"); analyseResults(postIncLessThanResults); System.out.println("Post increment, inequality test"); analyseResults(postIncNotEqualResults); System.out.println("Pre increment, less than test"); analyseResults(preIncLessThanResults); System.out.println("Pre increment, inequality test"); analyseResults(preIncNotEqualResults); } } 

对不起,如果我错了!

结果提示我 – testingi < maxValue每循环大约需要1.39毫秒,无论是使用前或后增量,但i != maxValue花了1.05毫秒。 这是一个24.5%的节省或32.5%的时间损失,这取决于你如何看待它。

当然,循环运行需要多长时间并不是你的瓶颈,但是这是一种优化,在你需要的时候很less有机会了解它。

尽pipe如此,我认为我仍然会坚持testing!

编辑

我testing了递减我,以及发现这并没有真正有效的时间所需要的 – for (int i = 2000000; i != 0; i--)for (int i = 0; i != 2000000; i++) for (int i = 2000000; i > 0; i--)for (int i = 0; i < 2000000; i++)的时间长度相同。

我已经决定列出最有说服力的答案,因为这个问题变得有点拥挤。

DenverCoder8的长凳标记显然值得一些认可,以及卢卡斯的循环编译版本。 Tim Gee已经显示了前后增量之间的差异,而User377178突出了<和!=的一些优缺点。 Tenacious Techhunter总的来说写了关于循环优化的内容,值得一提。

你有我的前5个答案。

  1. DenverCoder8
  2. 卢卡斯
  3. Tim Gee
  4. User377178
  5. 顽强的Techhunter

在generics代码中,您应该更喜欢使用!=运算符的版本,因为它只需要您的i具有相同的可比性 ,而<版本要求它具有相关性 。 后者比前者要求更强。 当一个较弱的要求足够好的时候,你通常应该避免更强的要求。

话虽如此,在你的具体情况下,如果int i能同样工作,而且在性能上也不会有任何差异。

我永远不会这样做:

 for(int i = 0; i != 5; ++i) 

我!= 5将它打开的可能性,我永远不会5.它很容易跳过它,并遇到一个无限循环或数组访问器错误。

 ++i 

虽然很多人知道你可以把++放在前面,但是有很多人不知道。 代码需要对人们可读,虽然这可能是使代码更快的微型优化,但是当有人需要修改代码并且确定代码的完成时,真的不值得额外的头痛。

我认为道格拉斯·克罗克福德(Douglas Crockford)有最好的build议,那就是不要使用++或者 – 。 它可能会变得太混乱(可能不是在一个循环中,但绝对是其他地方),它是一样容易写i = i + 1。他认为这只是一个坏习惯离开,我种同意看到一些残酷的“优化”的代码。

我认为克罗克福德正在与这些运营商,你可以让人们写这样的事情:

 var x = 0; var y = x++; y = ++x * (Math.pow(++y, 2) * 3) * ++x; alert(x * y); 

//答案是54 btw。

在这种情况下关心效率并不是一个好主意,因为编译器通常足够聪明,可以在代码执行时优化代码。

我曾经为一家为安全关键型系统生产软件的公司工作,其中一条规则是循环应该以“<”而不是“!”结尾。 有几个很好的理由:

  1. 你的控制variables可能会由于一些硬件问题或者一些内存入侵而跳到一个更高的值。

  2. 在维护中,可以在循环中增加迭代器的值,或者做一些类似“i + = 2”的操作,这样可以使循环永远滚动;

  3. 如果由于某种原因你的迭代器types从“int”更改为“float”(我不知道为什么有人会这样做,但无论如何…)一个精确的浮点比较是一个不好的做法。

(MISRA C ++编码标准(安全关键系统)也告诉你在规则6-5-2中更喜欢“<”而不是“!=”。我不知道我是否可以在这里发布规则定义因为MISRA是一个付费文件。)

关于++我或我+ +,我愿意使用++我。 当你使用基本types时,没有什么区别,但是当你使用STL迭代器时,预增量更有效率。 所以我总是用preincrement来习惯它。

为了logging,“for”循环的cobol等价物是: –

  PERFORM VARYING VAR1 FROM +1 BY +1 UNTIL VAR1 > +100 * SOME VERBOSE COBOL STATEMENTS HERE END-PERFORM. 

要么

 PERFORM ANOTHER-PARAGRAPH VARYING VAR2 BY +1 UNTIL TERMINATING-CONDITION WITH TEST AFTER. 

这有很多变化。 长期暴露在COBOL中的人们的主要困扰是,默认情况下, UNTIL实际上意味着WHILE即在循环的顶部执行testing,在循环variables递增之前以及在循环被处理。 你需要“ WITH TEST AFTER ”使其成为一个合适的UNTIL

第二个是可读性较差,我认为(如果只是因为“标准”的做法似乎是前者)。

数字文字撒在你的代码? 耻辱…

Donald Knuth曾经说过,回到正轨

我们应该忘记小效率,大约97%的时间:不成熟的优化是万恶之源。

所以,它真的归结为更容易parsing

所以…考虑到以上两点,以下哪一个对程序员来说更容易parsing?

 for (int i = 0; i < myArray.Length; ++i) for (int i = 0; i != myArray.Length; ++i) 

编辑:我知道在C#中的数组实现System.Collections.IList接口,但在其他语言不一定是真实的。

关于可读性。 作为喜欢Ruby的C#程序员,我最近为int写了一个扩展方法,允许使用下面的语法(如Ruby中):

 4.Times(x => MyAction(x)); 

总结两种select的利弊

优点!=

  • 当int被replace为某个迭代器或者通过模板parameter passing的types时,它有更好的工作机会,它会做预期的工作,效率更高。
  • 如果一些不可预测的事情发生在允许错误检测的ivariables上,将会“永久循环”

优点<

  • 正如其他人所说,与另一种简单types的人一样有效
  • 如果我在循环中增加,它将不会“永远”运行,或者5将被一些在循环运行时被修改的expression式replace
  • 将使用浮点types
  • 更可读 – 习惯的事情

我的结论是:

  1. 也许应该在大多数情况下使用!=版本,当我是离散的,而且比较的另一方不打算在循环中被篡改。

  2. 虽然<的存在将是一个明确的标志,我是简单types(或评估为简单types) 条件并非直截了当:我或条件额外修改循环和/或并行处理。

似乎没有人说过为什么历史上增量运算符++i比后缀i++适合小循环。

考虑前缀(增量和提取)和后缀(取和增量)的典型实现:

 // prefix form: increment and fetch UPInt& UPInt::operator++() { *this += 1; // increment return *this; // fetch } // posfix form: fetch and increment const UPInt UPInt::operator++(int) { const UPInt oldValue = *this; ++(*this); return oldValue; } 

请注意,前缀操作可以在原地完成,因为后缀需要另一个variables来跟踪旧值。 如果您不确定为什么这样,请考虑以下事项:

 int a = 0; int b = a++; // b = 0, the old value, a = 1 

在一个小循环中,后缀所需的额外分配在理论上可以使其变慢,所以老派的逻辑是前缀更有效率。 因此,许多C / C ++程序员都习惯于使用前缀forms。

然而,在别处指出的是,现代编译器是聪明的。 他们注意到,在for循环中使用后缀forms时,后缀的返回值是不需要的。 因此,没有必要跟踪旧的值,并且可以优化出来 – 保留使用前缀表单所得到的机器码。

呃…没关系,只要你不要在你的for循环中修改i 。 真正的“BEST”语法完全取决于你想要的结果。

如果你的索引不是int ,而是(比如说)一个C ++类,那么第二个例子可能会更有效率。

但是,正如所写的,你相信第二种forms更有效率是不正确的。 任何体面的编译器都会有一个简单的for循环的优秀的编码习惯用法,并且会为这两个例子生成高质量的代码。 更重要的是:

  • 在一个for循环中,执行严格的性能关键计算,索引算术将是整体负载的几乎可以忽略的部分。

  • 如果你的for循环对性能至关重要,而且不会执行大量的计算,以至于索引算术真的很重要,那么你几乎可以肯定地重构你的代码,在循环的每一个循环中做更多的工作。

当我第一次开始使用C语言编程时,我使用了++i表单来进行循环,只是因为当时使用的C编译器没有做太多的优化,而且在这种情况下会生成稍微高效的代码。

现在我使用++i表单,因为它的读数是“递增i”,而i++读数是“i递增”,任何英语老师都会告诉你避免被动语态。

底线是做任何似乎更可读的你。

我想最后归结为个人喜好。
我喜欢这个想法

 for(int i = 0; i < 5; i++) 

过度

 for(int i = 0; i != 5; ++i) 

由于某种原因,我有可能跳过5。 I know most times the chances on that happening are slim, but I think in the end its good practice.

We can use one more trick for this.

for (i = 5; i > 0; i–)

I suppose most of the compilers optimize the loops like this. I am not sure. Someone please verify.

Ultimately, the deciding factor as to what is more efficient is neither the language nor the compiler, but rather, the underlying hardware . If you're writing code for an embedded microcontroller like an 8051, counting up vs. counting down, greater or less than vs. not equals, and incrementing vs. decrementing, can make a difference to performance, within the very limited time scale of your loops.

While sufficient language and compiler support can (and often do) mitigate the absence of the instructions required to implement the specified code in an optimal but conceptually equivalent way, coding for the hardware itself guarantees performance, rather than merely hoping adequate optimizations exist at compile time.

And all this means, there is no one universal answer to your question, since there are so many different low-end microcontrollers out there.

Of much greater importance, however, than optimizing how your for loop iterates, loops, and breaks, is modifying what it does on each iteration . If causing the for loop one extra instruction saves two or more instructions within each iteration, do it ! You will get a net gain of one or more cycles! For truly optimal code, you have to weigh the consequences of fully optimizing how the for loop iterates over what happens on each iteration.

All that being said, a good rule of thumb is, if you would find it a challenge to memorize all the assembly instructions for your particular target hardware, the optimal assembly instructions for all variations of a “for” loop have probably been fully accounted for. You can always check if you REALLY care.

I see plenty of answers using the specific code that was posted, and integer. However the question was specific to 'for loops', not the specific one mentioned in the original post.

I prefer to use the prefix increment/decrement operator because it is pretty much guaranteed to be as fast as the postfix operator, but has the possibility to be faster when used with non-primitive types. For types like integers it will never matter with any modern compiler, but if you get in the habit of using the prefix operator, then in the cases where it will provide a speed boost, you'll benefit from it.

I recently ran a static analysis tool on a large project (probably around 1-2 million lines of code), and it found around 80 cases where a postfix was being used in a case where a prefix would provide a speed benefit. In most of these cases the benefit was small because the size of the container or number of loops would usually be small, but in other cases it could potentially iterate over 500+ items.

Depending on the type of object being incremented/decremented, when a postfix occurs a copy can also occur. I would be curious to find out how many compilers will spot the case when a postfix is being used when its value isn't referenced, and thus the copy could not be used. Would it generate code in that case for a prefix instead? Even the static analysis tool mentioned that some of those 80 cases it had found might be optimized out anyway, but why take the chance and let the compiler decide? I don't find the prefix operator to be at all confusing when used alone, it only becomes a burden to read when it starts getting used, inline, as part of a logic statement:

 int i = 5; i = ++i * 3; 

Having to think about operator precedence shouldn't be necessary with simple logic.

 int i = 5; i++; i *= 3; 

Sure the code above takes an extra line, but it reads more clearly. But with a for loop the variable being altered is its own statement, so you don't have to worry about whether it's prefix or postfix, just like in the code block above, the i++ is alone, so little thought is required as to what will happen with it, so this code block below is probably just as readable:

 int i = 5; ++i; i *= 3; 

As I've said, it doesn't matter all that much, but using the prefix when the variable is not being used otherwise in the same statement is just a good habit in my opinion, because at some point you'll be using it on a non-primitive class and you might save yourself a copy operation.

只是我的两分钱。

On many architectures, it is far easier to check whether something is zero that whether it is some other arbitrary integer, therefore if you truly want to optimize the heck out of something, whenever possible count down , not up (here's an example on ARM chips).

In general, it really depends on how you think about numbers and counting. I'm doing lots of DSP and mathematics, so counting from 0 to N-1 is more natural to me, you may be different in this respect.

FORTRAN's DO loop and BASIC's FOR loop implemented < (actually <= ) for positive increments. Not sure what COBOL did, but I suspect it was similar. So this approach was "natural" to the designers and users of "new" languages like C.

Additionally, < is more likely than != to terminate in erroneous situations, and is equally valid for integer and floating point values.

The first point above is the probable reason the style got started, the second is the main reason it continues.

I remember one code segment where the i was getting incremented by 2 instead of 1 due to some mistake and it was causing it to go in infinite loop. So it is better to have this loop as shown in the first option. This is more readable also. Because i != 5 and i < 5 conveys two different meaning to the reader. Also if you are increasing the loop variable then i<5 is suppose to end some point of time while i != 5 may never end because of some mistake.

It is not good approach to use as != 5. But

 for (int i =0; i<index; ++i) 

is more efficient than

 for(int i=0; i<index; i++) 

Because i++ first perform copy operation. For detailed information you can look operator overloading in C++.