2012-08-01 29 views
6

¿Es < más económico (más rápido) que <=, y del mismo modo, es > más económico (más rápido) que >=?¿Qué operador es más rápido (> o> =), (<o <=)?

Descargo de responsabilidad: Sé que podría medir pero solo en mi máquina y no estoy seguro si la respuesta podría ser "específica para la implementación" o algo así.

+12

Supongo que compilan una sola instrucción en la mayoría de las arquitecturas, pero que la respuesta es: ¿A quién le importa? – meagar

+1

Son casi equivalentes en términos de instrucciones de ensamblaje generadas, si eso es lo que preguntas. –

+8

Entiendo el espíritu detrás de su pregunta, pero: ¿lo hace por interés académico o porque cree que esto podría tener un impacto en el rendimiento de su aplicación? No lo hará. La diferencia, si está presente, será completamente ** inundada ** por otros factores en su aplicación. No por un factor de 2, o 10, sino por 1 mo más. Apuesto a que no serías capaz de medirlo en absoluto. –

Respuesta

3

varía, primero comience a examinar diferentes conjuntos de instrucciones y cómo los compiladores usan esos conjuntos de instrucciones. Tome el Openrisc 32 por ejemplo, que está claramente inspirado en mips pero tiene condiciones diferentes. Para el or32 hay instrucciones de comparar y establecer indicador, comparar estos dos registros si es menor o igual sin signo luego establecer el indicador, comparar estos dos registros si es igual establecer el indicador. Luego, hay dos ramas de instrucciones condicionales ramificadas en el conjunto de banderas y ramas en las banderas claras. El compilador debe seguir una de estas rutas, pero menos, que, menor que o igual, mayor que, etc. van a usar el mismo número de instrucciones, el mismo tiempo de ejecución para una rama condicional y el mismo tiempo de ejecución para no hacer el rama condicional.

Ahora bien, es cierto que para la mayoría de las arquitecturas, la realización de la bifurcación lleva más tiempo que no realizar la sucursal debido a tener que enjuagar y volver a llenar la tubería. Algunos predicen las ramificaciones, etc. para ayudar con ese problema.

Ahora algunas arquitecturas el tamaño de la instrucción puede variar, compare gpr0 y gpr1 vs compare gpr0 y el número inmediato 1234, puede requerir una instrucción más grande, verá esto mucho con x86 por ejemplo.entonces, aunque ambos casos pueden ser una rama, menos de cómo codificas menos dependiendo de qué registros contengan qué valores pueden hacer una diferencia en el rendimiento (seguro x86 hace un montón de canalización, mucho almacenamiento en caché, etc. para compensar estos problemas) Otro ejemplo similar es mips y or32, donde r0 es siempre cero, no es realmente un registro de propósito general, si le escribes no cambia, está cableado a cero, por lo que una comparación si es igual a 0 PODRÁS más que una comparación si es igual a algún otro número si se requiere una instrucción adicional o dos para llenar un gpr con ese inmediato para que la comparación pueda ocurrir, el peor de los casos es tener que desalojar un registro de la pila o memoria, para liberar el regístrese para poner lo inmediato allí para que la comparación pueda suceder.

Algunas arquitecturas tienen ejecución condicional como el brazo, para el brazo completo (no el pulgar) instrucciones que usted puede en función de cada instrucción de ejecutar, así que si tenía código

if(i==7) j=5; else j=9; 

el pseudo código para el brazo sería

cmp i,#7 
moveq j,#5 
movne j,#7 

no existe una derivación real, por lo que no hay problemas con la tubería que mueva rápidamente, muy rápido.

Una arquitectura a otra si es una comparación interesante, como se mencionó, mips, o32, tiene que realizar específicamente algún tipo de instrucción para la comparación, otros como x86, msp430 y la gran mayoría de las operaciones alu cambian las banderas , brazo y similares cambian las banderas si le dices que cambie las banderas, de lo contrario, no lo hagas como se muestra arriba. por lo que un bucle

while(--len) 
{ 
    //do something 
} 

el reste del 1 también establece los indicadores, si el material en el circuito era bastante simple que podría hacer que todo sea condicional, por lo que ahorrar en comparar y sucursales instrucciones por separado y ahorrar en el pena de la tubería. Mips resuelve esto un poco por comparación y rama son una instrucción, y ejecutan una instrucción después de la rama para guardar un poco en la tubería.

La respuesta general es que no verá la diferencia, el número de instrucciones, el tiempo de ejecución, etc. son los mismos para los distintos condicionales. casos especiales como pequeños inmediatos vs grandes inmediatos, etc. pueden tener un efecto para casos de esquina, o el compilador simplemente puede elegir hacerlo de manera diferente dependiendo de qué comparación haga. Si intenta volver a escribir su algoritmo para que le dé la misma respuesta, pero use un valor menor que en lugar de uno mayor que igual, podría cambiar el código lo suficiente como para obtener una secuencia de instrucciones diferente. Del mismo modo, si realiza una prueba de rendimiento demasiado simple, el compilador puede/optimizará la comparación completa y solo generará los resultados, lo que podría variar según el código de la prueba que cause una ejecución diferente. La clave de todo esto es desmontar las cosas que desea comparar y ver cómo difieren las instrucciones. Eso te dirá si deberías esperar ver diferencias en la ejecución.

10

TL; DR

Parece que hay poca o ninguna diferencia entre los cuatro operadores, ya que todos ellos realizan en aproximadamente el mismo tiempo para mí (puede ser diferente en diferentes sistemas!). Entonces, en caso de duda, solo use el operador que tenga más sentido para la situación (especialmente cuando se trata de C++).

Así, sin más preámbulos, aquí es la larga explicación:

Suponiendo comparación de enteros:

Como generada lejos como conjunto, los resultados son dependientes de la plataforma. En mi ordenador (Apple LLVM compilador 4.0, x86_64), los resultados (ensamblador generado es como sigue):

a < b (uses 'setl'): 

movl $10, -8(%rbp) 
movl $15, -12(%rbp) 
movl -8(%rbp), %eax 
cmpl -12(%rbp), %eax 
setl %cl 
andb $1, %cl 
movzbl %cl, %eax 
popq %rbp 
ret 

a <= b (uses 'setle'): 

movl $10, -8(%rbp) 
movl $15, -12(%rbp) 
movl -8(%rbp), %eax 
cmpl -12(%rbp), %eax 
setle %cl 
andb $1, %cl 
movzbl %cl, %eax 
popq %rbp 
ret 

a > b (uses 'setg'): 

movl $10, -8(%rbp) 
movl $15, -12(%rbp) 
movl -8(%rbp), %eax 
cmpl -12(%rbp), %eax 
setg %cl 
andb $1, %cl 
movzbl %cl, %eax 
popq %rbp 
ret 

a >= b (uses 'setge'): 

movl $10, -8(%rbp) 
movl $15, -12(%rbp) 
movl -8(%rbp), %eax 
cmpl -12(%rbp), %eax 
setge %cl 
andb $1, %cl 
movzbl %cl, %eax 
popq %rbp 
ret 

que no es realmente me dice mucho. Entonces, salteamos a un punto de referencia:

señores & señores, los resultados están en, creé el siguiente programa de prueba (soy consciente de que 'reloj' no es la mejor manera de calcular resultados como este, pero Tendré que hacer por ahora).

#include <time.h> 
#include <stdio.h> 

#define ITERS 100000000 

int v = 0; 

void testL() 
{ 
    clock_t start = clock(); 

    v = 0; 

    for (int i = 0; i < ITERS; i++) { 
     v = i < v; 
    } 

    printf("%s: %lu\n", __FUNCTION__, clock() - start); 
} 

void testLE() 
{ 
    clock_t start = clock(); 

    v = 0; 

    for (int i = 0; i < ITERS; i++) 
    { 
     v = i <= v; 
    } 

    printf("%s: %lu\n", __FUNCTION__, clock() - start); 
} 

void testG() 
{ 
    clock_t start = clock(); 

    v = 0; 

    for (int i = 0; i < ITERS; i++) { 
     v = i > v; 
    } 

    printf("%s: %lu\n", __FUNCTION__, clock() - start); 
} 

void testGE() 
{ 
    clock_t start = clock(); 

    v = 0; 

    for (int i = 0; i < ITERS; i++) { 
     v = i >= v; 
    } 

    printf("%s: %lu\n", __FUNCTION__, clock() - start); 
} 

int main() 
{ 
    testL(); 
    testLE(); 
    testG(); 
    testGE(); 
} 

Lo cual, en mi máquina (compilado con -O0), ME Este (5 carreras por separado) da:

 
testL: 337848 
testLE: 338237 
testG: 337888 
testGE: 337787 

testL: 337768 
testLE: 338110 
testG: 337406 
testGE: 337926 

testL: 338958 
testLE: 338948 
testG: 337705 
testGE: 337829 

testL: 339805 
testLE: 339634 
testG: 337413 
testGE: 337900 

testL: 340490 
testLE: 339030 
testG: 337298 
testGE: 337593 

Yo diría que las diferencias entre estos operadores son de menor importancia en el mejor, y don No tiene mucho peso en un mundo informático moderno.

+3

El código de ensamblado realmente decía mucho. Fue revelador que todos esos fragmentos deben tomarse exactamente al mismo tiempo, todas las circunstancias son iguales. 'setcc' toma 1 ciclo (excepto en P4, donde toma 3) independientemente de lo que sea' cc'. ¿Pero cómo es esto relevante? Los operadores de comparación casi nunca se utilizan de esta manera: comparar el rendimiento de 'jcc' (también lo mismo independientemente de' cc') parece más lógico. – harold

+1

Yo segundo @harold. La asamblea dice mucho: todas las comparaciones se realizan utilizando la misma instrucción 'cmpl' que hace el trabajo pesado de comparar sus argumentos. Básicamente, resta el segundo argumento del primero (descartando el resultado) y la ALU establece los bits correspondientes en el registro de indicadores. Estos pueden ser probados, amplificados o usados ​​para establecer valores de memoria/reg. –

+1

@harold Probablemente quiso decir "no me dice mucho" – hirschhornsalz

Cuestiones relacionadas