2010-09-29 12 views
20

Pregunta bastante simple. Sé que probablemente sea una pequeña optimización, pero eventualmente usarás suficientes declaraciones if para que importe.Es if (var == true) más rápido que if (var! = False)?

EDIT: Gracias a aquellos de ustedes que han proporcionado respuestas.

Para aquellos de ustedes que sienten la necesidad de golpearme, sepan que la curiosidad y la sed de conocimiento no se traducen en estupidez.

Y muchas gracias a todos los que brindaron críticas constructivas. No tenía conocimiento de la capacidad de decir si (var) hasta ahora. Estoy bonita seguro que la usaré ahora. ;)

+51

"eventualmente usará suficientes declaraciones if para que importe". bastante seguro de que no lo harás –

+29

"la optimización prematura es la raíz de todo mal" –

+2

La optimización prematura es la raíz de todos los males –

Respuesta

68

En primer lugar, la única forma de responder la pregunta de rendimiento es mídalo. Pruébelo usted mismo y lo descubrirá.

En cuanto a lo que hace el compilador: le recuerdo que "si" es solo un goto condicional.Cuando se tiene

if (x) 
    Y(); 
else 
    Z(); 
Q(); 

el compilador genera que ya sea como:

evaluate x 
branch to LABEL1 if result was false 
call Y 
branch to LABEL2 
LABEL1: 
call Z 
LABEL2: 
call Q 

o

evaluate !x 
branch to LABEL1 if result was true 

dependiendo de si es más fácil para generar el código para provocar el "normal" o " "resultado" invertido para lo que sea que "x" sea. Por ejemplo, si tiene if (a<=b), podría ser más fácil generarlo como (if !(a>b)). O viceversa; depende de los detalles del código exacto que se compila.

De todos modos, sospecho que tienes peces más grandes para freír. Si le importa el rendimiento, utilice un generador de perfiles y encuentre lo más lento y luego solucione ese. No tiene sentido preocuparse por las optimizaciones de nanosegundos cuando probablemente está desperdiciando milisegundos enteros en otro lugar de su programa.

+13

"Usar un generador de perfiles" - tan verdadero. No puedo contar cuántas veces he "sabido" dónde está el problema de rendimiento, solo para que el perfil de una carrera me lo muestre mal. – codekaizen

+3

No solo sospecho, diría que se acerca una certeza matemática de que tienes peces más grandes para freír. – Epaga

+1

La pregunta original es clara por sí misma. No veo la razón para afirmar lo obvio: a. "mídelo usted mismo" y b. "tienes un pez más grande para freír, usa un perfilador". Es una buena pregunta científica y la estás respondiendo como un ingeniero. Típico. - Mis 2 centavos de una respuesta científica es: si a y b son, por ejemplo, enteros, flotantes, etc., entonces si (a> b) es definitivamente más rápido que si (! (a <= b)). Ahora la verdadera pregunta es: ¿se optimizará (a <= b) a (a> b) por el compilador? –

11

No hace ninguna diferencia medible, independientemente de la cantidad de iteraciones que use en su programa.

(Uso if (var) lugar; no es necesario el desorden visual de las comparaciones.)

+0

si (var) serían las mismas operaciones que si (var == verdadero) o (var! = Falso). Sin embargo, puede preferirlo desde el punto de vista del estilo. –

+2

@Graphain, es casi seguro que preferiría 'if (var)' suponiendo que 'var' se llamara sensiblemente para indicar inmediatamente que era un booleano, y qué significa. De hecho, si 'if (var == true)' fuera más legible, diría que hay un problema con el nombre del bool. –

+0

De acuerdo, pero solo quería que los demás sepan que no hay diferencia en el rendimiento, ya que es una cuestión de rendimiento. –

2

no hace ninguna diferencia, y el compilador es libre de intercambiar a voluntad.

Por ejemplo, podría escribir

if (!predicate) 
    statement1; 
else 
    statement2; 

y el compilador es libre de emitir código equivalente a

if (predicate) 
    statement2; 
else 
    statement1; 

o viceversa.

11

Hará una diferencia absolutamente nula, porque el compilador casi seguramente compilará las dos declaraciones en el mismo código binario.

La (pseudo) de montaje, o bien ser:

test reg1, reg2 
br.true somewhere 
; code for false case 

somewhere: 
; code for true case 

o

test reg1, reg2 
br.false somewhere 
; code for true case 

somewhere: 
; code for false case 

¿Cuál de los que el compilador decide no dependerá de si se escribe == true o != false. Más bien, es una optimización que el compilador hará en función del tamaño del código de caso verdadero y falso y quizás de otros factores.

Como acotación al margen, el código del núcleo de Linux en realidad no tratar de optimizar el uso de estas ramas LIKELY y UNLIKELY macros para sus if condiciones, así que supongo que es posible controlar manualmente.

+0

+1 Buen punto acerca de qué desencadena la optimización. –

18

No hará ninguna diferencia en absoluto. El uso del reflector se puede ver que el código:

private static void testVar(bool var) 
{ 
    if (var == true) 
    { 
     Console.WriteLine("test"); 
    } 

    if (var != false) 
    { 
     Console.WriteLine("test"); 
    } 

    if (var) 
    { 
     Console.WriteLine("test"); 
    } 
} 

crea la IL:

.method private hidebysig static void testVar(bool var) cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: brfalse.s L_000d 
    L_0003: ldstr "test" 
    L_0008: call void [mscorlib]System.Console::WriteLine(string) 
    L_000d: ldarg.0 
    L_000e: brfalse.s L_001a 
    L_0010: ldstr "test" 
    L_0015: call void [mscorlib]System.Console::WriteLine(string) 
    L_001a: ldarg.0 
    L_001b: brfalse.s L_0027 
    L_001d: ldstr "test" 
    L_0022: call void [mscorlib]System.Console::WriteLine(string) 
    L_0027: ret 
} 

Así el compilador (en .Net 3.5) todos ellos se traduce en la ldarg.0, brfalse.s conjunto de instrucciones .

41

¿Sabías que en los procesadores x86 es más eficiente hacer x ^= x donde x es un entero de 32 bits, que hacer x = 0? Es cierto, y por supuesto tiene el mismo resultado. Por lo tanto, cada vez que se puede ver x = 0 en el código, uno puede reemplazarlo por x ^= x y ganar eficiencia.

¿Alguna vez ha visto x ^= x en muchos códigos?

La razón por la que no lo ha hecho no es solo porque la ganancia de eficiencia es leve, sino porque este es precisamente el tipo de cambio que un compilador (si compila a código nativo) o jitter (si compila IL o similar) hará . Desmontar algunos códigos x86 y no es inusual ver el ensamblaje equivalente a x ^= x, aunque el código que se compiló para hacer esto casi seguro tenía x = 0 o quizás algo mucho más complicado como x = 4 >> 6 o x = 32 - y donde el análisis del código muestra que y siempre contendrá 32 en este punto, y así sucesivamente.

Por esta razón, a pesar de que x ^= x se sabe que es más eficiente, la única efecto de que en la gran mayoría de los casos, sería hacer que el código menos legible (la única excepción sería donde no hacer x ^= y estaba implicado en un algoritmo que se usaba y estábamos haciendo un caso donde x y y eran lo mismo aquí, en este caso x ^= x haría más claro el uso de ese algoritmo, mientras que x = 0 lo ocultaría).

En 99.999999 por ciento de casos, lo mismo se aplicará a su ejemplo. En el 0 restante.000001% de casos que debería, pero existe una diferencia de eficiencia entre algún tipo extraño de operador que reemplaza y, el compilador no puede resolver el uno al otro. De hecho, 0.000001% exagera el caso, y solo lo menciono porque estoy bastante seguro de que si lo hubiera intentado lo suficiente podría escribir algo donde uno sea menos eficiente que el otro. Normalmente las personas no se esfuerzan por hacerlo.

Si alguna vez mira su propio código en reflector, probablemente encontrará algunos casos en los que se ve muy diferente al código que escribió. La razón de esto es que es la ingeniería inversa de la IL de su código, en lugar de su código en sí, y de hecho una cosa que a menudo encontrará es if(var == true) o if(var != false) convirtiéndose en if(var) o incluso en if(!var) con if y else bloques invertidos.

Mire más profundo y verá que aún se realizan más cambios ya que hay más de una manera de desollar al mismo gato. En particular, es interesante observar cómo se convierten las declaraciones switch a IL; a veces se convierte en el equivalente a un montón de declaraciones if-else if, y a veces se convierte en una búsqueda en una tabla de saltos que podrían realizarse, dependiendo de lo que parezca más eficiente en el caso en cuestión.

Mire más profundamente y se realizan otros cambios cuando se compila en código nativo.

No voy a estar de acuerdo con aquellos que hablan de "optimización prematura" solo porque pregunta sobre la diferencia de rendimiento entre dos enfoques diferentes, porque el conocimiento de tales diferencias es algo bueno, solo usa ese conocimiento prematuramente es prematuro (por definición). Pero un cambio que se compilará no es ni prematuro ni una optimización, es solo un cambio nulo.

+1

"El conocimiento de tales diferencias es algo bueno": esto presenta un buen argumento para ejecutar usando el Desensamblador IL en su código para verificar realmente si existe alguna diferencia. Que, dicho sea de paso, puede ser más preciso que simplemente el uso de un generador de perfiles. Equal IL puede confiar 100% en ser igual en tiempo de ejecución. Igual perfil no le puede dar una confianza perfecta, ya que sus datos de perfil pueden tener ruido y su máquina de prueba puede tener características diferentes a otras máquinas que ejecutarán la aplicación. También generalmente lleva más tiempo. – Brian

+0

Sí. Además, sabiendo que esto no hace ninguna diferencia, o que X es más eficiente que Y en el caso A, pero menos en el caso B le permite a uno dar sentido a lo que el perfilador le dice cuando es hora de preocuparse por tales cosas. –

+3

'¿Sabía que en los procesadores x86 es más eficiente hacer x^= x donde x es un entero de 32 bits? Esto ya no es el caso. 'mov ax, 0' es una instrucción más pequeña, no tiene los efectos secundarios de' xor ax, ax' e incluso entonces los procesadores han avanzado al nivel donde el comando mov solo tiene un período más rápido. –

1

Saber cuál de estos dos casos específicos es más rápido es un nivel de detalle que rara vez (o nunca) se requiere en un lenguaje de alto nivel. Quizás necesite saber si su compilador es pobre en optimizaciones. Sin embargo, si su compilador es tan malo, probablemente sea mejor en general obtener uno mejor si es posible.

Si está programando en ensamblaje, es más probable que su conocimiento de los dos casos sea mejor. Otros ya han dado el desglose de la asamblea con respecto a las declaraciones de la sucursal, por lo que no me molestaré en duplicar esa parte de la respuesta. Sin embargo, un elemento que se ha omitido en mi opinión es el de la comparación.

Es concebible que un procesador pueda cambiar los indicadores de estado al cargar 'var'. Si es así, entonces si 'var' es 0, entonces el indicador de cero se puede establecer cuando la variable se carga en un registro. Con tal procesador, no se requeriría una comparación explícita contra FALSO. El ensamblaje equivalente pseudo-código sería ...

load 'var' into register 
branch if zero or if not zero as appropriate 

Usando este mismo procesador, si tuviera que probar contra TRUE, el conjunto de pseudo-código sería ...

load 'var' into register 
compare that register to TRUE (a specific value) 
branch if equal or if not equal as appropriate 

En práctica, ¿algún procesador se comporta así? No lo sé, otros estarán más informados que yo. Conozco a algunos que no se comportan de esta manera, pero no sé todo.

Suponiendo que algunos procesadores se comporten como en el escenario descrito anteriormente, ¿qué podemos aprender? SI (y eso es un IF grande) te vas a preocupar por esto, evita probar booleanos contra valores explícitos ...

if (var == TRUE) 
if (var != FALSE) 

y utilizar uno de los siguientes tipos booleanos para probar ...

if (var) 
if (!var) 
8

Una regla de oro que por lo general funciona es "Si sabe que hacen lo mismo, entonces el compilador sabe demasiado ".

Si el compilador sabe que las dos formas arrojan el mismo resultado, , elegirá la más rápida,.

Por lo tanto, suponga que son igualmente rápidos, hasta que su generador de perfiles le indique lo contrario.

4

Las otras respuestas son buenas, yo sólo quería añadir:

Esto no es una cuestión significativa, porque supone una relación 1: 1 entre la notación y la IL resultante o código nativo.

No hay. Y eso es cierto incluso en C++, e incluso en C. Tienes que llegar hasta el código nativo para que esa pregunta tenga sentido.

Editado para añadir:

Los desarrolladores del primer compilador Fortran (ca. 1957) se sorprendieron un día cuando la revisión de su salida. Estaba emitiendo un código que no era obviamente correcto (aunque lo era); en esencia, tomaba decisiones de optimización que no eran obviamente correctas (aunque lo eran).

La moraleja de esta historia: los compiladores han sido más inteligentes que las personas durante más de 50 años. No intente burlarlos a menos que esté preparado para examinar sus resultados y/o realizar pruebas exhaustivas de rendimiento.

5

Optimice siempre para facilitar la comprensión. Esta es una regla cardinal de programación, en lo que a mí respecta. No debe micro-optimizar, o incluso optimizar en todohasta usted saber saber que necesita hacerlo, y dónde debe hacerlo. Es un caso muy raro que exprimir cada onza de rendimiento sea más importante que el mantenimiento y es aún más raro que seas tan increíble que sabes dónde optimizar al escribir el código inicialmente.

Además, este tipo de cosas se optimizan automáticamente en cualquier lenguaje decente.

tl; dr no molestar

+0

Estoy con tu en la optimización de micro, pero debe tener en cuenta que algunos de los impactos de rendimiento más dramáticos vienen del diseño general o algoritmos utilizados en la aplicación. Estos pueden ser muy caros de arreglar si te vas demasiado lejos en el camino. –

Cuestiones relacionadas