ciclo for incremento de 2 en 2 java
Razones técnicas detrás del formateo al incrementar en 1 en un bucle ''for''? (30)
En toda la web, los ejemplos de código tienen bucles que se ven así:
for(int i = 0; i < 5; i++)
mientras usaba el siguiente formato:
for(int i = 0; i != 5; ++i)
Hago esto porque creo que es más eficiente, pero, ¿esto realmente importa en la mayoría de los casos?
Bueno ... está bien siempre y cuando no modifiques i
dentro de tu bucle for. La verdadera sintaxis "MEJOR" para esto depende completamente de su resultado deseado.
Cambié a usar !=
Hace más de 20 años después de leer el libro de Dijkstra titulado "A Disciplina de la programación" . En su libro Dijkstra observó que las condiciones de continuación más débiles conducen a postcondiciones más fuertes en construcciones de bucle.
Por ejemplo, si modificamos su construcción para exponer i
después del bucle, la condición posterior del bucle de puño sería i >= 5
, mientras que la condición posterior del segundo bucle es mucho más fuerte i == 5
. Esto es mejor para razonar sobre el programa en términos formales de invariantes de ciclo, condiciones posteriores y precondiciones más débiles.
Con respecto al uso de ++ i en lugar de i ++, no hace una diferencia con la mayoría de los compiladores, sin embargo, ++ podría ser más eficiente que i ++ cuando se utiliza como un iterador.
Depende del idioma
Los textos en C ++ a menudo sugieren que el segundo formato funcionará con iteradores que se pueden comparar (! =) Directamente, pero no con una condición mayor a menor. También el incremento previo puede ser más rápido que el incremento posterior ya que no hay necesidad de una copia de la variable para comparar, sin embargo los optimizadores pueden manejar esto.
Para enteros, ambos forman trabajos. La expresión común para C es la primera, mientras que para C ++ es la segunda.
Para el uso de C # y Java, me gustaría hacer un seguimiento de todas las cosas.
En C ++ también existe la función std :: for_each que requiere el uso de un functor que, para casos simples, es probablemente más compleja que cualquiera de los ejemplos aquí y Boost FOR_EACH, que puede parecer el foreach de C # pero complejo en su interior.
El segundo es menos legible, creo (aunque solo sea porque la práctica "estándar" parece ser la primera).
En cuanto a la legibilidad Siendo un programador de C # que le gusta Ruby, recientemente escribí un método de extensión para int que permite la siguiente sintaxis (como en Ruby):
4.Times(x => MyAction(x));
En el código genérico, debe preferir la versión con el operador !=
, Ya que solo requiere que su i
sea igualmente comparable , mientras que la versión <
requiere que sea relacionalmente comparable . Este último es un requisito más fuerte que el anterior. En general, debería preferir evitar requisitos más fuertes cuando un requisito más débil es perfectamente suficiente.
Habiendo dicho eso, en su caso específico, si ambos funcionarán igual de bien y no habrá ninguna diferencia en el rendimiento.
En realidad, hay cuatro permutaciones en lo que das. Para sus dos:
for(int i = 0; i < 5; i++)
for(int i = 0; i != 5; ++i)
Podemos agregar:
for(int i = 0; i < 5; ++i)
for(int i = 0; i != 5; i++)
En la mayoría de las máquinas modernas con compiladores modernos no debería sorprender que estas tengan exactamente la misma eficacia. Podría ser casi posible que algún día te encuentres programando para un procesador pequeño donde haya una diferencia entre las comparaciones de igualdad y las comparaciones menores.
En algún caso, puede tener más sentido para una mente en particular con un caso particular pensar "menos que" o "no igual" dependiendo de la razón por la que elegimos 0 y 5, pero incluso entonces, lo que hace que uno parezca obvio a uno codificador no puede con otro.
De manera más abstracta, estas son de las formas:
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++)
Una diferencia obvia aquí es que en dos casos someType
debe tener un significado para <
y para el resto debe tener un significado para !=
. Los tipos para los cuales !=
Está definido y <
no son son bastante comunes, incluyendo bastantes objetos iteradores en C ++ (y potencialmente en C # donde el mismo enfoque que los iteradores de STL es posible y algunas veces útil, pero no como idiomático, directamente respaldado por bibliotecas comunes ni a menudo útiles ya que hay idiomas rivales con más apoyo directo). Vale la pena señalar que el enfoque STL está diseñado específicamente para incluir punteros dentro del conjunto de tipos de iteradores válidos. Si tiene el hábito de usar el STL, considerará los formularios con !=
Mucho más idiomático incluso cuando se aplica a enteros. Personalmente, una mínima cantidad de exposición fue suficiente para convertirlo en mi instinto.
Por otro lado, aunque definir <
y no !=
Sería más raro, es aplicable a casos en los que reemplazamos el incremento con un aumento diferente en el valor de i
, o donde puedo ser alterado dentro del ciclo.
Entonces, hay casos definidos en ambos lados donde uno u otro es el único enfoque.
Ahora para ++i
vs i++
. De nuevo con enteros y cuando se llama directamente en lugar de a través de una función que devuelve el resultado (y las posibilidades son incluso entonces), el resultado práctico será exactamente el mismo.
En algunos lenguajes de estilo C (sin sobrecarga de operadores), los enteros y los punteros son los únicos casos que existen. Podríamos inventar artificialmente un caso en el que el incremento se llame a través de una función solo para cambiar la forma en que funciona, y es probable que el compilador siga convirtiéndolo en la misma cosa de todos modos.
C ++ y C # nos permiten anularlos. En general, el prefijo ++
funciona como una función que hace:
val = OneMoreThan(val);//whatever OneMoreThan means in the context.
//note that we assigned something back to val here.
return val;
Y el postfix ++
funciona como una función que hace:
SomeType copy = Clone(val);
val = OneMoreThan(val);
return copy;
Ni C ++ ni C # coinciden perfectamente con los anteriores (hice deliberadamente que mi pseudocódigo no coincidiera), pero en cualquier caso puede haber una copia o quizás dos hechos. Esto puede o no ser costoso. Puede o no ser evitable (en C ++ a menudo podemos evitarlo por completo para la forma del prefijo devolviendo this
y en el sufijo devolviendo el vacío). Puede o no optimizarse en nada, pero sigue siendo que podría ser más eficiente hacer ++i
que i++
en ciertos casos.
Más particularmente, existe la ligera posibilidad de una ligera mejora en el rendimiento con ++i
, y con una clase grande podría incluso ser considerable, pero impidiendo que alguien sobrescriba en C ++ para que los dos tengan significados completamente diferentes (una idea bastante mala) no es generalmente es posible que esto sea al revés. Como tal, adquirir el hábito de favorecer el prefijo sobre el postfix significa que puede obtener una mejora mayone una vez cada mil, pero no perderá, por lo que es un hábito que los codificadores C ++ suelen tener.
En resumen, no hay absolutamente ninguna diferencia en los dos casos dados en su pregunta, pero puede haber en variantes de la misma.
Estoy de acuerdo con lo que se ha dicho sobre la legibilidad: es importante tener un código que sea fácil de leer para el mantenedor, aunque es de esperar que sea quien sea, comprenda los incrementos previos y posteriores.
Dicho esto, pensé que haría una prueba simple y obtendría datos sólidos sobre cuál de los cuatro bucles corre más rápido. Estoy en una computadora con especificaciones promedio, compilando con javac 1.7.0.
Mi programa crea un bucle for, iterando 2.000.000 de tiempo sobre nada (para no saturar los datos interesantes con cuánto tiempo lleva hacer lo que está en el bucle for). Utiliza los cuatro tipos propuestos anteriormente, y multiplica los resultados, repitiendo 1000 veces para obtener un promedio.
El código real es:
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);
}
}
Lo siento si he copiado eso en mal!
Los resultados me sorprendieron: la prueba de i < maxValue
tomó aproximadamente 1,39 ms por ciclo, ya sea usando incrementos previos o posteriores, pero i != maxValue
tomó 1,05 ms. Eso es un ahorro de 24.5% o una pérdida de tiempo del 32.5%, dependiendo de cómo lo mires.
Por supuesto, el tiempo que tarda en ejecutarse un bucle for probablemente no sea su cuello de botella, pero este es el tipo de optimización que es útil conocer, en las raras ocasiones en que lo necesita.
¡Sin embargo, creo que me quedaré con las pruebas por menos tiempo!
Editar
También probé la disminución de i, y descubrí que esto realmente no tiene efecto en el tiempo que toma - for (int i = 2000000; i != 0; i--)
y for (int i = 0; i != 2000000; i++)
ambos toman el mismo período de tiempo, como lo hacen for (int i = 2000000; i > 0; i--)
y for (int i = 0; i < 2000000; i++)
.
He decidido enumerar las respuestas más informativas ya que esta pregunta se está llenando un poco.
La marca de banco DenverCoder8 claramente merece un reconocimiento así como las versiones compiladas de los bucles de Lucas . Tim Gee ha mostrado las diferencias entre el incremento previo y posterior, mientras que el User377178 ha resaltado algunos de los pros y los contras de <y! =. Tenacious Techhunter ha escrito sobre optimizaciones de bucle en general y vale la pena mencionarlo.
Ahí tienes mis 5 respuestas principales.
La forma
for (int i = 0; i < 5; i++)
es idiomático , por lo que es más fácil de leer para los programadores de C experimentados. Especialmente cuando se usa para iterar sobre una matriz. Debería escribir código idiomático siempre que sea posible, ya que lee más rápido.
También es un poco más seguro cuando modificas i dentro del ciclo o utilizas un incremento diferente a 1. Pero es algo menor. Lo mejor es diseñar cuidadosamente su ciclo y agregar algunos argumentos para detectar suposiciones rotas con anticipación.
Literales numéricos salpicados en su código? Para vergüenza...
Volviendo a la pista, Donald Knuth dijo una vez
Deberíamos olvidarnos de las pequeñas eficiencias, digamos aproximadamente el 97% del tiempo: la optimización prematura es la raíz de todo mal.
Entonces, realmente se reduce a lo que es más fácil de analizar
Entonces ... teniendo en cuenta ambas cosas, ¿cuál de las siguientes opciones es más fácil de analizar para un programador?
for (int i = 0; i < myArray.Length; ++i)
for (int i = 0; i != myArray.Length; ++i)
Edición: soy consciente de que las matrices en C # implementan la interfaz System.Collections.IList, pero eso no es necesariamente cierto en otros idiomas.
No es una buena idea preocuparse por la eficiencia en esos casos, ya que su compilador suele ser lo suficientemente inteligente como para optimizar su código cuando sea capaz de hacerlo.
He trabajado para una empresa que produce software para sistemas críticos para la seguridad, y una de las reglas era que el ciclo debería terminar con un "<" en lugar de un! =. Hay varias buenas razones para eso:
Su variable de control podría saltar a un valor más alto por algún problema de hw o alguna invasión de memoria;
En el mantenimiento, uno podría incrementar el valor de su iterador dentro del bucle, o hacer algo como "i + = 2", y esto haría que su bucle se lance para siempre;
Si por alguna razón su tipo de iterador cambia de "int" a "float" (no sé por qué alguien haría eso, pero de todos modos ...) una comparación exacta para los puntos de flotación es una mala práctica.
(El estándar de codificación MISRA C ++ (para sistemas críticos para la seguridad) también le indica que prefiera el "<" en lugar de "! =" En la regla 6-5-2. No sé si puedo publicar la definición de la regla aquí porque MISRA es un documento pagado).
Acerca de ++ i o i ++, preferiría usar ++ i. No hay diferencia para eso cuando se trabaja con tipos básicos, pero cuando se usa un iterador STL, el preincremento es más eficiente. Así que siempre uso el preincremento para acostumbrarme.
Nunca haria esto:
for(int i = 0; i != 5; ++i)
i! = 5 lo deja abierto ante la posibilidad de que nunca vuelva a ser 5. Es muy fácil omitirlo y ejecutar un bucle infinito o un error de acceso a la matriz.
++i
Aunque mucha gente sabe que puedes poner ++ en primer plano, hay muchas personas que no lo hacen. El código debe ser legible para las personas, y aunque podría ser una micro optimización para hacer que el código vaya más rápido, realmente no vale la pena el dolor de cabeza adicional cuando alguien tiene que modificar el código y calcular por qué se hizo.
Creo que Douglas Crockford tiene la mejor sugerencia y es no usar ++ o - en absoluto. Puede ser demasiado confuso (puede no estar en un bucle pero definitivamente en otros lugares) a veces y es igual de fácil escribir i = i + 1. Cree que es solo un mal hábito para salir, y yo como que acuerde después de ver algún código "optimizado" atroz.
Creo que lo que Crockford está logrando es que con esos operadores puedas hacer que la gente escriba cosas como:
var x = 0;
var y = x++;
y = ++x * (Math.pow(++y, 2) * 3) * ++x;
alert(x * y);
// la respuesta es 54 por cierto.
Para el registro, el equivalente de cobol del bucle "for" es:
PERFORM VARYING VAR1
FROM +1 BY +1
UNTIL VAR1 > +100
* SOME VERBOSE COBOL STATEMENTS HERE
END-PERFORM.
o
PERFORM ANOTHER-PARAGRAPH
VARYING VAR2 BY +1
UNTIL TERMINATING-CONDITION
WITH TEST AFTER.
Hay muchas variaciones sobre esto. Lo más importante para los pueblos cuyas mentes no han sido dañadas por la exposición prolongada a COBOL es, por defecto, UNTIL
realidad significa WHILE
la prueba se realiza en la parte superior del ciclo, antes de que la variable de ciclo se incremente y antes que el cuerpo del loop es procesado Necesita el " WITH TEST AFTER
" para que sea UNTIL
adecuado.
Para resumir los pros y los contras de ambas opciones
Pros de! =
- cuando int se reemplaza con algún iterador o se pasa un tipo a través del argumento de la plantilla, es más probable que funcione, hará lo que se espera y será más eficiente.
- ''repetirá siempre'' si ocurre algo imprevisto en la variable i permitiendo la detección de errores
Pros de <
- como otros dicen es tan eficiente como el otro con tipos simples
- no se ejecutará "para siempre" si se aumenta en el ciclo o 5 se reemplaza con alguna expresión que se modifique mientras el ciclo se está ejecutando
- funcionará con el tipo de flotador
- más legible - cuestión de acostumbrarse
Mis conclusiones:
Quizás la versión ! = Debería usarse en la mayoría de los casos, cuando soy discreto y lo es, así como el otro lado de la comparación no está destinado a ser alterado dentro del ciclo.
Mientras que la presencia de < sería una señal clara de que el i es de tipo simple (o evalúa al tipo simple) y la condición no es sencilla: i o la condición se modifica adicionalmente dentro del ciclo y / o procesamiento paralelo.
Parece que nadie ha declarado la razón por la que históricamente el operador preincremento, ++i
, ha sido preferido sobre el postfix i++
, para pequeños bucles.
Considere una implementación típica del prefijo (incremento y recuperación) y el postfijo (recuperación e incremento):
// 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;
}
Tenga en cuenta que la operación de prefijo puede realizarse in situ , mientras que el sufijo requiere otra variable para realizar un seguimiento del valor anterior. Si no está seguro de por qué esto es así, considere lo siguiente:
int a = 0;
int b = a++; // b = 0, the old value, a = 1
En un bucle pequeño, esta asignación adicional requerida por el postfix podría, teóricamente, hacerlo más lento, por lo que la lógica de la vieja escuela es que el prefijo es más eficiente. Como tal, muchos programadores de C / C ++ se han quedado con el hábito de usar la forma de prefijo.
Sin embargo, se observa en otros lugares el hecho de que los compiladores modernos son inteligentes. Observan que cuando se usa el formulario postfix en un ciclo for, el valor de retorno del postfijo no es necesario. Como tal, no es necesario realizar un seguimiento del valor anterior y puede optimizarse, dejando el mismo código de máquina que obtendría al usar el formulario de prefijo.
Si la regla de incremento cambia ligeramente, inmediatamente tiene un ciclo infinito. Yo prefiero la primera condición final.
Si por alguna razón salta a 50 en el ciclo, su versión se repetirá para siempre. El i < 5
es un control de cordura.
Si su índice no fuera una int
, sino (por ejemplo) una clase de C ++, entonces sería posible que el segundo ejemplo sea más eficiente.
Sin embargo, tal como está escrito, su creencia de que la segunda forma es más eficiente es simplemente incorrecta. Cualquier compilador decente tendrá expresiones idiomáticas de codegen excelentes para un bucle for simple, y producirá código de alta calidad para cualquier ejemplo. Más al punto:
En un bucle for que está haciendo cálculos pesados de rendimiento crítico, la aritmética de índice será una parte casi insignificante de la carga total.
Si su bucle for es crítico para el rendimiento y no realiza cálculos pesados de modo que la aritmética del índice realmente importe, casi con seguridad debe reestructurar su código para hacer más trabajo en cada pasada del ciclo.
Todo el mundo ama sus micro-optimizaciones, pero esto no marcaría la diferencia por lo que puedo ver. Recopilé las dos variaciones con g ++ en adelante para procesadores intel sin optimizaciones sofisticadas y los resultados son para
para (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
para (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
Creo que "jle" y "jne" deberían traducirse en instrucciones igualmente rápidas en la mayoría de las arquitecturas. Entonces para el rendimiento no debes distinguir entre los dos. En general, estoy de acuerdo en que el primero es un poco más seguro y también creo que es más común.
EDITAR (2 años después): dado que este hilo recibió nuevamente mucha atención, me gustaría agregar que será difícil responder a esta pregunta en general. Las versiones de código que son más eficientes no están específicamente definidas por C-Standard [PDF] (y lo mismo se aplica a C ++ y probablemente también a C #).
Sección 5.1.2.3 Ejecución del programa
§1 Las descripciones semánticas en este estándar internacional describen el comportamiento de una máquina abstracta en la que los problemas de optimización son irrelevantes.
Pero es razonable suponer que un compilador moderno producirá un código igualmente eficiente y creo que, en casos muy raros, la "prueba de bucle" y la "expresión de conteo" serán el cuello de botella de un bucle for.
En cuanto al gusto, escribo for(int i = 0; i < 5; ++i)
.
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.
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.
Solo mis dos centavos.
I think in the end it boils down to personal preference.
I like the idea of
for(int i = 0; i < 5; i++)
encima
for(int i = 0; i != 5; ++i)
due to there being a chance of the value of i jumping past 5 for some reason. I know most times the chances on that happening are slim, but I think in the end its good practice.
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++.
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.
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.
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. No estoy seguro. Someone please verify.
When I first started programming in C, I used the ++i
form in for loops simply because the C compiler I was using at the time did not do much optimization and would generate slightly more efficient code in that case.
Now I use the ++i
form because it reads as "increment i", whereas i++
reads as "i is incremented" and any English teacher will tell you to avoid the passive voice.
The bottom line is do whatever seems more readable to you.