2012-02-26 7 views
13

Así que me encuentro haciendo algo así como el siguiente patrón a menudo. En lugar de:patrón java: cuando tiene sentido usar variables temporales

if (map.containsKey(someKey)) { 
    Value someValue = map.get(someKey); 
    ... 
} 

Para no recorrer el mapa en dos ocasiones (y ya sé que mi mapa no almacena valores nulos), lo haré:

Value someValue = map.get(someKey); 
if (someValue != null) { 
    ... 
} 

Esto parece que sea una pena patrón dado que las operaciones Map realizan un número decente de operaciones y el optimizador, supongo, no es lo suficientemente inteligente como para optimizarlo.

Pero luego me encuentro haciendo patrones similares en otras situaciones. Por ejemplo, ¿debo almacenar el resultado someMethod() en una variable temporal en lugar de hacer la llamada dos veces? Claramente, no puedo llamar al someMethod() dos veces si hay efectos secundarios, pero ¿cuándo tiene sentido llamarlo solo una vez desde el punto de vista de la optimización?

if (someObject.someMethod() != null) { 
    processSomehow(someObject.someMethod()); 
} 

Sé que esto raya en una pregunta "no constructiva", así que estoy en busca de respuestas que presentan algunos datos y referencias en lugar de sólo una conjetura.

  • ¿Cuándo tiene sentido hacer esto y cuándo no?
  • ¿Cómo debo evaluar el "costo" del someMethod() para determinar cuándo se debe usar una variable temporal?
  • Para métodos simples como obtener métodos, ¿podría esto interferir con el compilador del punto de acceso o el optimizador y producir un código menos eficiente?

Para la posteridad, no estoy preguntando "cómo puedo mejorar la velocidad de mi programa existente". Estoy tratando de descubrir "qué patrón debo usar cuando escribo el código futuro".

Gracias por cualquier información.

+6

Creo que la respuesta a todas estas preguntas implica la palabra "profiler" ... –

+4

Claramente, si 'someMethod()' es costoso o tiene efectos secundarios, no desea llamarlo dos veces. Java no contiene (todavía) una palabra clave 'idempotent' (trae recuerdos viejos de' reducible' en PL/1 :-) –

+0

Buen punto @Jim. He editado mi pregunta para dejar en claro que no estoy hablando de efectos secundarios. Pero de lo que estoy hablando es de cómo evaluar el "gasto" del método. – Gray

Respuesta

11

¿Cómo debo evaluar el "costo" de someMethod() para determinar cuándo se debe usar una variable temporal?

Simplemente observe la implementación de someMethod(). Si solo devuelve un campo, como lo haría un método getter típico, no hay necesidad de declarar una variable local (en lo que respecta al rendimiento). Si el método crea objetos, llama a la base de datos o lee el contenido del archivo, generalmente es conveniente almacenar en caché el valor de retorno.

Personalmente, no me preocupan los problemas de rendimiento causados ​​por declarar una variable temporal. Solo guardo el valor de retorno en lugar de llamar a un método dos veces. El código es mucho más fácil de leer y comprender (a menos que nombrar todas aquellas variables simplemente temp ;-))

+0

Un par de buenos detalles (base de datos, nuevo objeto) allí @Andreas, pero puede proporcionar más detalles sobre cómo se evalúa el costo del método a. No es tan simple como "mirar". – Gray

+2

Mi umbral es bastante bajo. Todo lo que sea más caro que 'return mValue;' se almacenará en caché;) - por cierto, creo que podemos estimar la complejidad/costo simplemente mirando el código. –

+0

Estaba tratando de obtener más detalles. "Todo más caro, luego devuelve mValue"; es lo que estaba buscando. Gracias. – Gray

5

Es evidente que no puedo llamar algunMetodo() dos veces si hay efectos secundarios, pero cuándo tiene ¿Tiene sentido llamarlo solo una vez desde el punto de vista de la optimización?

Cuando haya demostrado que es importante utilizar un generador de perfiles. Pero acostúmbrate a no llamar a los métodos dos veces seguidas de todos modos, ya que facilitará el mantenimiento (¿qué pasa si el cheque cambia y te olvidas de cambiarlo en ambos lugares?). Use variables temporales generosamente, son increíblemente baratas.

+2

Creo que "liberalmente" puede necesitar ser calificado. Tirar basura a tus métodos con variables de un solo uso también tiene sus desventajas. –

+1

En realidad, tiendo a introducir uno cada vez que necesito hacer una operación dos veces sin la variable. –

+0

No tengo tiempo para perfilar mi aplicación cada vez que necesito evaluar si necesito o no una variable temporal. ¿Hay algún patrón que busque @larsmans mientras está codificando? – Gray

5

Como regla general uso una var local cuando la necesito más de una vez. Puede darle a esa var local un nombre bonito y todo el mundo sabe de qué se trata esta var. Desde el punto de vista del rendimiento: los vars locales son baratos y los métodos quizás complejos.

Especialmente cuando someMethod() es costoso como un método sincronizado, esto debería funcionar mucho mejor. Pero cuando someMethod() es un método sincronizado, debe ser invocado por múltiples hilos en concurrencia y luego hace una diferencia llamar al método dos veces o almacenar su valor de retorno y reutilizarlo ...

... Otro punto mencionar que las llamadas a métodos posteriores no tienen que devolver los mismos datos. Por lo tanto, sus ejemplos con/sin var local no son siempre sustitutos válidos para las llamadas a métodos posteriores. Pero creo que ya has considerado esto.

+0

Nunca he comentado esto, pero gracias por su respuesta. Le di un +1. Buenos puntos. – Gray

1

me recuerda una discusión con respecto a .NET estilo Dictionary uso similar: What is more efficient: Dictionary TryGetValue or ContainsKey+Item?

En general, como se ha señalado en los comentarios, no conoce los puntos calientes de rendimiento en su programa hasta que se ejecuta con datos reales bajo un perfilador.

Además, en general, los compiladores en idiomas imperativos no pueden optimizar las llamadas repetidas, debido a los posibles efectos secundarios. Cada llamada a una función puede devolver un nuevo valor. Y, por cierto, esa también es una de las razones por las que usted no debería confiar en las llamadas repetidas. Hoy tal vez sepa que una función o una propiedad es libre de efectos secundarios y las llamadas repetidas devuelven el mismo valor, pero mañana lo cambiará y agregará un generador aleatorio dentro y se romperá toda lógica con llamadas repetidas.

2

Para mí, todo se reduce a la lógica del programa y no al rendimiento. Así

if (map.containsKey(someKey)) { 
    Value someValue = map.get(someKey); 
    ... 
} 

es no equivalente al código en todos los casos:

Value someValue = map.get(someKey); 
if (someValue != null) { 
    ... 
} 

Considere un mapa donde el valor puede ser nulo para una clave dada. Si la pieza ... es null segura, el código se comportará de manera diferente.

También este código tiene problemas lógicos. Considere esto:

BufferedRead reader = ...; 
if (reader.readLine() != null) { 
    processSomehow(reader.readLine()); 
} 

Aquí está claro que readLine debe ser llamado dos veces. Otro dominio problemático para este tipo de código es el multihilo: el código espera que el valor no cambie, pero otro hilo podría hacer excatly esto.

Sumario Mantenga su código lo más nítido y correcto posible. Esto significa que si espera que un valor no cambie y lo necesite varias veces, utilice una variable para decirle a a alguien esto.

+0

No estaba hablando de lógica @ A.H .. Entiendo cuándo necesito usar variables temporales. Estaba tratando de averiguar cuándo fue recomendado. – Gray

+0

@Gray: Entendí tu punto. Mi punto es: la lógica es lo primero.Cuando la lógica dicta que se espera el mismo valor, se deben usar variables. Por lo tanto, ambos ejemplos son lógicamente erróneos o dudosos. Entonces: _no_ usar variables es la verdadera excepción. –

+0

Mi código no es ni lógicamente incorrecto ni dudoso. Sé que mi 'Mapa' no acepta nulos o nunca los almaceno allí. Nadie más parece haber necesitado esa aclaración. Tu respuesta olvidó por completo mi pregunta. – Gray

2

Varias personas están citando Knuth (implícita o explícitamente), pero creo que esto siempre tiene sentido como un patrón de diseño (y los casos en que null es una clave permisible, as pointed out by A.H. se convierten en mucho más clara). En mi opinión, es similar al caso de pasar una variable por referencia constante en C++ en lugar de por valor.Hay dos razones para hacer esto: (a) comunica que el valor no cambiará, pero la razón principal por la que se hace es que es marginalmente más rápido. No importa en la mayoría de las llamadas individuales, pero cuando cada rutina pasa todos los parámetros por valor, lo nota. Es un buen hábito para estar. El caso que mencionas no es tan común, por lo que no es tan importante, pero yo diría que todavía es un buen hábito.

+0

Gracias. Buenos puntos. – Gray

Cuestiones relacionadas