¿Se debe usar <o <= en un bucle for [cerrado]

124

Si tuviera que repetir un ciclo 7 veces, ¿usaría:

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

o:

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

Hay dos consideraciones:

  • actuación
  • legibilidad

Para el rendimiento, supongo Java o C #. ¿Importa si se usa "menor que" o "menor o igual que"? Si tiene conocimiento de un idioma diferente, indique cuál.

Para facilitar la lectura, estoy asumiendo matrices basadas en 0.

UPD: Mi mención de matrices basadas en 0 puede haber confundido las cosas. No estoy hablando de iterar a través de elementos de matriz. Solo un bucle general.

Hay un buen punto a continuación sobre el uso de una constante para explicar qué es este número mágico. Así que si tuviera " int NUMBER_OF_THINGS = 7" entonces " i <= NUMBER_OF_THINGS - 1" se vería raro, ¿no?

Eugene Katz
fuente
Yo diría: si se ejecuta en toda la matriz, nunca reste ni agregue ningún número al lado izquierdo.
Letterman

Respuestas:

287

El primero es más idiomático . En particular, indica (en un sentido basado en 0) el número de iteraciones. Al usar algo basado en 1 (por ejemplo, JDBC, IIRC), podría sentir la tentación de usar <=. Entonces:

for (int i=0; i < count; i++) // For 0-based APIs

for (int i=1; i <= count; i++) // For 1-based APIs

Esperaría que la diferencia de rendimiento sea insignificantemente pequeña en el código del mundo real.

Jon Skeet
fuente
30
Está casi garantizado que no habrá una diferencia de rendimiento. Muchas arquitecturas, como x86, tienen instrucciones de "saltar menos o igual en la última comparación". La forma más probable de ver una diferencia de rendimiento sería en algún tipo de lenguaje interpretado que se implementó de manera deficiente.
Wedge
3
¿Considerarías usar! = En su lugar? Yo diría que eso establece claramente i como un contador de bucles y nada más.
yungchin
21
Por lo general no lo haría. Es muy poco familiar. También corre el riesgo de entrar en un ciclo muy, muy largo si alguien incrementa accidentalmente i durante el ciclo.
Jon Skeet el
55
La programación genérica con iteradores STL exige el uso de! =. Esto (doble incremento accidental) no ha sido un problema para mí. Estoy de acuerdo en que los índices <(o> para descender) son más claros y convencionales.
Jonathan Graehl
2
Recuerde, si realiza un bucle en la longitud de una matriz con <, el JIT optimiza el acceso a la matriz (elimina las comprobaciones vinculadas). Por lo tanto, debería ser más rápido que usar <=. Sin embargo, no lo he comprobado
configurador del
72

Ambos bucles iteran 7 veces. Yo diría que el que tiene un 7 es más legible / más claro, a menos que tenga una buena razón para el otro.

Steve Losh
fuente
Recuerdo cuando empecé a aprender Java. Odiaba el concepto de un índice basado en 0 porque siempre he usado índices basados ​​en 1. Por lo tanto, siempre usaría la variante <= 6 (como se muestra en la pregunta). En mi propio detrimento, porque eventualmente me confundiría más sobre cuándo salió realmente el ciclo for. Es más simple usar el <
James Haug
55

Recuerdo de mis días cuando hicimos la Asamblea 8086 en la universidad, fue más eficiente:

for (int i = 6; i > -1; i--)

ya que hubo una operación JNS que significa Saltar si no hay señal. Usar esto significaba que no había búsqueda de memoria después de cada ciclo para obtener el valor de comparación y tampoco comparar. En la actualidad, la mayoría de los compiladores optimizan el uso del registro, por lo que la memoria ya no es importante, pero aún así obtiene una comparación no requerida.

Por cierto, poner 7 o 6 en tu ciclo es introducir un " número mágico ". Para una mejor legibilidad, debe usar una constante con un nombre revelador de intención. Me gusta esto:

const int NUMBER_OF_CARS = 7;
for (int i = 0; i < NUMBER_OF_CARS; i++)

EDITAR: La gente no está entendiendo el montaje, por lo que obviamente se requiere un ejemplo más completo:

Si lo hacemos para (i = 0; i <= 10; i ++) debe hacer esto:

    mov esi, 0
loopStartLabel:
                ; Do some stuff
    inc esi
                ; Note cmp command on next line
    cmp esi, 10
    jle exitLoopLabel
    jmp loopStartLabel
exitLoopLabel:

Si lo hacemos para (int i = 10; i> -1; i--), entonces puede salirse con la suya:

    mov esi, 10
loopStartLabel:
                ; Do some stuff
    dec esi
                ; Note no cmp command on next line
    jns exitLoopLabel
    jmp loopStartLabel
exitLoopLabel:

Acabo de comprobar y el compilador de C ++ de Microsoft no hace esta optimización, pero lo hace si usted lo hace:

for (int i = 10; i >= 0; i--) 

Entonces, la moraleja es que si está utilizando Microsoft C ++ †, y ascendente o descendente no hace ninguna diferencia, para obtener un bucle rápido debe usar:

for (int i = 10; i >= 0; i--)

en lugar de cualquiera de estos:

for (int i = 10; i > -1; i--)
for (int i = 0; i <= 10; i++)

Pero, francamente, obtener la legibilidad de "for (int i = 0; i <= 10; i ++)" es normalmente mucho más importante que perder un comando de procesador.

† Otros compiladores pueden hacer cosas diferentes.

Martin Brown
fuente
44
El caso del "número mágico" ilustra muy bien por qué generalmente es mejor usar <que <=.
Rene Saarsoo
11
Otra versión es "for (int i = 10; i--;)". Algunas personas usan "for (int i = 10; i -> 0;)" y pretenden que la combinación -> significa va a.
Zayenz
2
+1 para el código de ensamblaje
neuro
1
pero cuando llega el momento de usar el contador de bucles, por ejemplo, para la indexación de matrices, debe hacer lo 7-ique va a empañar cualquier optimización que obtenga al contar hacia atrás.
Lie Ryan
@Lie, esto solo se aplica si necesita procesar los artículos en orden hacia adelante. Con la mayoría de las operaciones en este tipo de bucles, puede aplicarlos a los elementos del bucle en el orden que desee. Por ejemplo, si está buscando un valor, no importa si comienza al final de la lista y continúa o al principio de la lista y baja (suponiendo que no pueda predecir qué final de la lista es su elemento es probable que sea y el almacenamiento en memoria caché no es un problema).
Martin Brown
27

Siempre uso <array.length porque es más fácil de leer que <= array.length-1.

también con <7 y dado que sabe que está comenzando con un índice 0, debería ser intuitivo que el número es el número de iteraciones.

Omar Kooheji
fuente
Siempre debe tener cuidado de verificar el costo de las funciones de Longitud cuando las use en un bucle. Por ejemplo, si usa strlen en C / C ++, aumentará enormemente el tiempo que lleva hacer la comparación. Esto se debe a que strlen tiene que iterar toda la cadena para encontrar su respuesta, que es algo que probablemente solo desee hacer una vez en lugar de cada iteración de su ciclo.
Martin Brown
2
@ Martin Brown: en Java (y creo que C #), String.length y Array.length es constante porque String es inmutable y Array tiene una longitud inmutable. Y dado que String.length y Array.length es un campo (en lugar de una llamada de función), puede estar seguro de que deben ser O (1). En el caso de C ++, bueno, ¿por qué demonios estás usando C-string en primer lugar?
Lie Ryan
18

Visto desde un punto de vista optimizador, no importa.

Visto desde un punto de vista de estilo de código, prefiero <. Razón:

for ( int i = 0; i < array.size(); i++ )

es mucho más legible que

for ( int i = 0; i <= array.size() -1; i++ )

también <te da el número de iteraciones de inmediato.

Otro voto a favor de <es que podría evitar muchos errores accidentales ocasionales.

erlando
fuente
10

@ Chris, su afirmación sobre que .Length es costoso en .NET en realidad no es cierto y, en el caso de los tipos simples, es exactamente lo contrario.

int len = somearray.Length;
for(i = 0; i < len; i++)
{
  somearray[i].something();
}

en realidad es más lento que

for(i = 0; i < somearray.Length; i++)
{
  somearray[i].something();
}

El último es un caso optimizado por el tiempo de ejecución. Dado que el tiempo de ejecución puede garantizar que i es un índice válido en la matriz, no se realizan verificaciones de límites. En el primero, el tiempo de ejecución no puede garantizar que no haya sido modificado antes del ciclo y obliga a las verificaciones de límites en la matriz para cada búsqueda de índice.

Jeff Mc
fuente
En Java .Length puede ser costoso en algunos casos. stackoverflow.com/questions/6093537/for-loop-optimization b'coz llama a .lenghtOR .size. No estoy seguro pero no estoy seguro, pero solo quiero asegurarme.
Ravi Parekh
6

No hace una diferencia efectiva cuando se trata de rendimiento. Por lo tanto, usaría el que sea más fácil de entender en el contexto del problema que está resolviendo.

Phil Wright
fuente
5

Yo prefiero:

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

Creo que eso se traduce más fácilmente a "iterar a través de un bucle 7 veces".

No estoy seguro de las implicaciones de rendimiento: sospecho que cualquier diferencia se compilaría.

Dominic Rodger
fuente
4

En Java 1.5 solo puedes hacer

for (int i: myArray) {
    ...
}

así que para el caso de la matriz no tienes que preocuparte.

JesperE
fuente
4

No creo que haya una diferencia de rendimiento. Sin embargo, la segunda forma es definitivamente más legible, no es necesario restar mentalmente una para encontrar el último número de iteración.

EDITAR: veo que otros no están de acuerdo. Para mí personalmente, me gusta ver los números de índice reales en la estructura de bucle. Tal vez sea porque recuerda más a la 0..6sintaxis de Perl , que sé que es equivalente a (0,1,2,3,4,5,6). Si veo un 7, tengo que verificar el operador al lado para ver que, de hecho, el índice 7 nunca se alcanza.

Adam Bellaire
fuente
Depende de si crees que el "número de la última iteración" es más importante que el "número de iteraciones". Con una API basada en 0, siempre diferirán en 1 ...
Jon Skeet
4

Yo diría que use la versión "<7" porque eso es lo que leerá la mayoría de las personas, por lo que si las personas leen con descuido su código, podrían interpretarlo incorrectamente.

No me preocuparía si "<" es más rápido que "<=", solo busque legibilidad.

Si desea aumentar la velocidad, considere lo siguiente:

for (int i = 0; i < this->GetCount(); i++)
{
  // Do something
}

Para aumentar el rendimiento, puede reorganizarlo ligeramente para:

const int count = this->GetCount();
for (int i = 0; i < count; ++i)
{
  // Do something
}

Observe la eliminación de GetCount () del bucle (porque se consultará en cada bucle) y el cambio de "i ++" a "++ i".

Mark Ingram
fuente
Me gusta más el segundo porque es más fácil de leer, pero ¿realmente vuelve a calcular this-> GetCount () cada vez? Esto me sorprendió cuando cambié esto y el recuento me obligó a hacer lo mismo ... mientras esto-> GetCount ()
osp70
GetCount () se llamaría cada iteración en el primer ejemplo. Solo se llamaría una vez en el segundo ejemplo. Si necesita que se llame a GetCount () cada vez, téngalo en el bucle, si no intenta mantenerlo afuera.
Mark Ingram
Sí, lo probé y tienes razón, mis disculpas.
osp70
¿Qué diferencia hace usar ++ i sobre i ++?
Rene Saarsoo
Un aumento de velocidad menor al usar ints, pero el aumento podría ser mayor si está incrementando sus propias clases. Básicamente ++ i incrementa el valor real, luego devuelve el valor real. i ++ crea una var. temporal, incrementa la var real, luego devuelve temp. No es necesaria la creación de var con ++ i.
Mark Ingram
4

En C ++, prefiero usar !=, que se puede usar con todos los contenedores STL. No todos los iteradores de contenedores STL son menos que comparables.

loco
fuente
Esto me asusta un poco solo porque hay una pequeña posibilidad externa de que algo pueda repetir el contador sobre mi valor deseado, lo que hace que este sea un bucle infinito. Las posibilidades son remotas y fáciles de detectar, pero el <se siente más seguro.
Rob Allen
2
Si hay un error como ese en su código, probablemente sea mejor bloquearse y quemarse que continuar en silencio :-)
Esta es la respuesta correcta: pone menos demanda en su iterador y es más probable que aparezca si hay un error en su código. El argumento para <es miope. Tal vez un bucle infinito sería malo en los años 70 cuando pagaba por el tiempo de CPU. '! =' es menos probable que oculte un error.
David Nehme
1
El bucle sobre iteradores es un caso completamente diferente al bucle con un contador. ! = es esencial para los iteradores.
DJClayworth
4

Edsger Dijkstra escribió un artículo sobre esto en 1982, donde argumenta a favor de <= i <superior:

Hay un número natural más pequeño. La exclusión del límite inferior —como en b) yd) - obliga a una subsecuencia que comienza en el número natural más pequeño, el límite inferior como se menciona en el reino de los números no naturales. Eso es feo, por lo que para el límite inferior preferimos el ≤ como en a) yc). Considere ahora las subsecuencias que comienzan en el número natural más pequeño: la inclusión del límite superior obligaría a este último a ser antinatural para cuando la secuencia se haya reducido al vacío. Eso es feo, por lo que para el límite superior preferimos <como en a) yd). Concluimos que la convención a) es preferible.

Martijn
fuente
3

Primero, no use 6 o 7.

Mejor usar:

int numberOfDays = 7;
for (int day = 0; day < numberOfDays ; day++){

}

En este caso es mejor que usar

for (int day = 0; day <= numberOfDays  - 1; day++){

}

Aún mejor (Java / C #):

for(int day = 0; day < dayArray.Length; i++){

}

Y aún mejor (C #)

foreach (int day in days){// day : days in Java

}

El ciclo inverso es realmente más rápido, pero dado que es más difícil de leer (si no lo hacen otros programadores), es mejor evitarlo. Especialmente en C #, Java ...

Carra
fuente
2

Estoy de acuerdo con la multitud que dice que el 7 tiene sentido en este caso, pero agregaría que en el caso donde el 6 es importante, digamos que desea dejar en claro que solo está actuando sobre objetos hasta el sexto índice, luego el <= es mejor ya que hace que los 6 sean más fáciles de ver.

tloach
fuente
entonces, <tamaño en comparación con i <= LAST_FILLED_ARRAY_SLOT
Chris Cudmore
2

En la universidad, recuerdo algo acerca de que estas dos operaciones son similares en el tiempo de cálculo en la CPU. Por supuesto, estamos hablando a nivel de montaje.

Sin embargo, si estás hablando de C # o Java, realmente no creo que uno vaya a aumentar la velocidad sobre el otro, los pocos nanosegundos que obtienes probablemente no valen la confusión que introduzcas.

Personalmente, crearía el código que tiene sentido desde el punto de vista de la implementación comercial y me aseguraría de que sea fácil de leer.

casademora
fuente
2

Esto cae directamente bajo la categoría de "Hacer que el código incorrecto parezca incorrecto" .

En los lenguajes de indexación basados ​​en cero, como Java o C #, las personas están acostumbradas a las variaciones de la index < countcondición. Por lo tanto, aprovechar esta convención de facto haría que los errores fuera de uno sean más obvios.

En cuanto al rendimiento: cualquier buen compilador que valga la huella de su memoria debería ser un problema.

Ryan Delucchi
fuente
2

Como un pequeño aparte, al recorrer una matriz u otra colección en .Net, encuentro

foreach (string item in myarray)
{
    System.Console.WriteLine(item);
}

para ser más legible que el bucle numérico para. Por supuesto, esto supone que el contador real Int no se usa en el código del bucle. No sé si hay un cambio de rendimiento.

Rob Allen
fuente
Esto también requiere que no modifique el tamaño de la colección durante el ciclo.
Jeff B
Lo mismo para For (i = 0, i <myarray.count, i ++)
Rob Allen
1

Hay muchas buenas razones para escribir i <7. Tener el número 7 en un bucle que itera 7 veces es bueno. El rendimiento es efectivamente idéntico. Casi todo el mundo escribe i <7. Si está escribiendo para facilitar la lectura, use el formulario que todos reconocerán al instante.

DJClayworth
fuente
1

Siempre he preferido:

for ( int count = 7 ; count > 0 ; -- count )
kenny
fuente
¿Cuál es tu razón de ser? Estoy realmente interesado
Chris Cudmore
eso está perfectamente bien para el bucle inverso ... si alguna vez necesitas tal cosa
ShoeLace
1
Una razón es que en el nivel de uP, comparar con 0 es rápido. Otra es que me lee bien y el conteo me da una indicación fácil de cuántas veces más quedan.
Kenny
1

Tener el hábito de usar <lo hará consistente tanto para usted como para el lector cuando esté iterando a través de una matriz. Será más simple para todos tener una convención estándar. Y si está utilizando un lenguaje con matrices basadas en 0, entonces <es la convención.

Esto casi ciertamente importa más que cualquier diferencia de rendimiento entre <y <=. Apunte a la funcionalidad y la legibilidad primero, luego optimice.

Otra nota es que sería mejor tener el hábito de hacer ++ i en lugar de i ++, ya que fetch and increment requiere un temporal e increment y fetch no. Para los enteros, su compilador probablemente optimizará la eliminación temporal, pero si su tipo de iteración es más complejo, es posible que no pueda hacerlo.

JohnMcG
fuente
1

No uses números mágicos.

¿Por qué son las 7? (o 6 para el caso).

use el símbolo correcto para el número que desea usar ...

En cuyo caso creo que es mejor usar

for ( int i = 0; i < array.size(); i++ )
Krakkos
fuente
1

Los operadores '<' y '<=' son exactamente el mismo costo de rendimiento.

El operador '<' es un estándar y más fácil de leer en un bucle de base cero.

Usar ++ i en lugar de i ++ mejora el rendimiento en C ++, pero no en C #: no sé acerca de Java.

Jeff B
fuente
1

Como la gente ha observado, no hay diferencia en ninguna de las dos alternativas que mencionó. Solo para confirmar esto, hice algunos benchmarking simples en JavaScript.

Puedes ver los resultados aquí . Lo que no está claro de esto es que si cambio la posición de la primera y segunda prueba, los resultados de esas 2 pruebas cambian, esto es claramente un problema de memoria. Sin embargo, la tercera prueba, una en la que invierto el orden de la iteración, es claramente más rápida.

David Wees
fuente
¿Por qué comienzas con i = 1 en el segundo caso?
Eugene Katz
Hrmm, ¿probablemente un error tonto? Agité esto bastante rápido, tal vez 15 minutos.
David Wees
1

Como todos dicen, es costumbre usar iteradores indexados 0 incluso para cosas fuera de los arrays. Si todo comienza en 0y termina en n-1, y los límites inferiores son siempre <=y los límites superiores son siempre <, hay mucho menos pensamiento que debe tener al revisar el código.

efímero
fuente
1

Gran pregunta Mi respuesta: use el tipo A ('<')

  • Usted ve claramente cuántas iteraciones tiene (7).
  • La diferencia entre dos puntos finales es el ancho del rango
  • Menos caracteres lo hacen más legible
  • Con mayor frecuencia tiene el número total de elementos en i < strlen(s)lugar del índice del último elemento, por lo que la uniformidad es importante.

Otro problema es con toda esta construcción. iaparece 3 veces en él, por lo que se puede escribir mal. La construcción for-loop dice cómo hacer en lugar de qué hacer . Sugiero adoptar esto:

BOOST_FOREACH(i, IntegerInterval(0,7))

Esto es más claro, se compila para ejecutar exactamente las mismas instrucciones, etc. Pídame el código de IntegerInterval si lo desea.

Pavel Radzivilovsky
fuente
1

Tantas respuestas ... pero creo que tengo algo que agregar.

Mi preferencia es que los números literales muestren claramente qué valores tomará "i" en el ciclo . Entonces, en el caso de iterar a través de una matriz basada en cero:

for (int i = 0; i <= array.Length - 1; ++i)

Y si solo está haciendo un bucle, sin iterar a través de una matriz, contar de 1 a 7 es bastante intuitivo:

for (int i = 1; i <= 7; ++i)

La legibilidad supera el rendimiento hasta que lo perfiles, ya que probablemente no sepas qué hará el compilador o el tiempo de ejecución con tu código hasta entonces.

Nick Westgate
fuente
1

También podría usar !=en su lugar. De esa manera, obtendrá un bucle infinito si comete un error en la inicialización, lo que hace que el error se note antes y cualquier problema que cause se limite a quedarse atrapado en el bucle (en lugar de tener un problema mucho más tarde y no encontrarlo eso).

Brian
fuente
0

Creo que ambos están bien, pero cuando hayas elegido, mantente en uno u otro. Si está acostumbrado a usar <=, intente no usar <y viceversa.

Prefiero <=, pero en situaciones en las que está trabajando con índices que comienzan en cero, probablemente intente usar <. Sin embargo, todo es preferencia personal.

seanyboy
fuente
0

Estrictamente desde un punto de vista lógico, debes pensar que < countsería más eficiente que <= countpor la razón exacta que también <=probará la igualdad.

Henry B
fuente
No lo creo, en ensamblador se reduce a cmp eax, 7 jl LOOP_START o cmp eax, 6 jle LOOP_START ambos necesitan la misma cantidad de ciclos.
Treb
<= se puede implementar como! (>)
JohnMcG
! (>) sigue siendo dos instrucciones, pero Treb está en lo cierto de que JLE y JL usan la misma cantidad de ciclos de reloj, por lo que <y <= toman la misma cantidad de tiempo.
Jacob Krall