2012-02-28 12 views
6

A menudo, me vienen a través de código en la que se utiliza repetidamente la método Getter/abusado de obtener algún valor o pasarlo como un parámetro de método, por ejemplo:Accessor Método de optimización de rendimiento y

public class Test { 
    public void someMethod() { 
     if(person.getName() != null && person.getName().equalsIgnoreCase("Einstein")) { 
      method1(person.getName()); 
     } 
     method2(person.getName()); 
     method3(person.getName()); 
     method4(person.getName()); 
    } 
} 

general I Código que, como a continuación:

public class Test { 
    public void someMethod() { 
     String name = person.getName(); 
     if(name != null && name.equalsIgnoreCase("Einstein")) { 
      method1(name); 
     } 
     method2(name); 
     method3(name); 
     method4(name); 
    } 

en mi opinión, hay una considerable ventaja de memoria/rendimiento en la asignación del captador a una variable y su uso, como captadores son los métodos de Java y marcos de uso de la pila. ¿Existe realmente una ventaja considerable en la codificación de esa manera? }

+0

Por qué no probarlo y ver? La ventaja de rendimiento sería más fácil de medir, aunque todavía necesita atención para obtener resultados precisos ... – DNA

+3

Depende de cuán computacionalmente costoso sea 'getName'. Si 'getName' es un método de miles de líneas lleno de bloqueos y sincronización, entonces sí, es posible que tenga algunos problemas. Por otra parte, puede que no. Además, si múltiples llamadas pueden devolver diferentes valores, ambos enfoques pueden ser semánticamente diferentes. –

+4

No puede usar "getter" y "best practice" en el mismo lugar, a menos que el mensaje sea "el uso de métodos getter es lo contrario de las mejores prácticas". – DwB

Respuesta

16

¿Ha perfila su opinión últimamente.

Rendimiento:

Esto podría haber sido un micro-optimización que era algo que se ocupa de en 1999-2001 en un pre-1.2 JVM e incluso entonces habría interrogado a menos que algunos números mostraron graves de otra manera.

Las implementaciones de JIT modernas le dirán que hoy su opinión está fuera de lugar.

Las implementaciones modernas de compiladores realizan todo tipo de optimizaciones que hacen que pensar sobre este tipo de cosas sea una pérdida de tiempo en Java. El JIT simplemente lo hace aún más un desperdicio de preocupación.

Lógica:

En una situación concurrente, los dos bloques de código no son lógicamente equivalentes, si desea ver los cambios, por lo que la copia local y evitará. Dependiendo de lo que quieras hacer, uno u otro enfoque podría crear errores muy sutiles no deterministas que serían muy difíciles de precisar en un código más complicado.

Especialmente si lo que se devolvió fue algo mutable a diferencia de String que es inmutable. Entonces, incluso la copia local podría estar cambiando, a menos que haya hecho una clonación profunda, y eso se torna realmente fácil de equivocarse rápidamente.

Preocúpate con hacerlo correctamente, entonces medida y luego optimizar lo que es importante con tal de que no hace que el código sea menos fácil de mantener.

La JVM inline cualquier llamada a final miembros de instancia y retire la llamada al método, si no hay nada en la llamada al método que no sea el return this.name; Se sabe que no hay lógica en el método de acceso y sabe la referencia es final por lo sabe que puede alinear el valor porque no cambiará.

Para ello

person.getName() != null && person.getName().equalsIgnoreCase("Einstein") 

se expresa más correctamente como

person != null && "Einstein".equalsIgnoreCase(person.getName()) 

porque no hay posibilidad de tener un Refactoring NullPointerException

:

refactorización IDE moderno t ools elimina cualquier argumento sobre tener que cambiar el código en muchos lugares también.

2

Si su razonamiento para hacer algo en Java es porque "podría funcionar mejor", lo está haciendo mal. El compilador JIT optimizará el caso trivial public String getName(){return name;} de distancia. En su lugar, debe tomar decisiones basadas en "¿Es esta la manera correcta de OOP de hacerlo?"

Desde el punto de vista de OOP, solo use el captador. Entonces, una subclase puede anular el método según sea necesario para hacer otras cosas sofisticadas (como ignorar el nombre "Einstein").

+1

El póster no dejó de usar un captador. El getter solo se llama una vez en el mismo bloque de código en lugar de llamarse repetidamente. La anulación de método no tendría efecto sobre la corrección del ejemplo. –

+1

Sería si el estado devuelto cambiara en función de cuántas veces se llamara al descriptor de acceso getXXX :-) –

+0

, entonces no sería un verdadero getter si tuviera efectos secundarios –

4

No hay diferencia de rendimiento. Si hay, es trivialmente pequeño.

Más importante aún, los dos ejemplos de código en realidad se comportan de manera completamente diferente en presencia de varios hilos.

Digamos que a la mitad, alguien llama al setName y cambia el nombre. ¡Ahora su código está pasando por un camino completamente inesperado! Esta ha sido la causa principal de un par de vulnerabilidades de seguridad históricas (incluso a nivel de kernel).

Tenga en cuenta que no estoy abogando por copiar a ciegas todos los resultados que obtenga en las variables locales. Solo estoy señalando una trampa potencial.

1

Sin duda es mucho más legible si coloca el valor en una variable local, por lo que debe hacerlo y no porque funciona mejor.

Otra diferencia masiva es si getName() tiene un efecto secundario (que no debería tener pero no puede confiar ciegamente en otras clases), o si está trabajando en un sistema de subprocesos múltiples. En el primer caso, la diferencia es obvia, en el segundo caso, otro hilo puede cambiar el valor getName() devuelve mientras está ejecutando su extracto.

En ambos casos lo que probablemente desee es invocar solo getName() una vez.

0

Estoy de acuerdo en principio con que la preoptimización es mala, pero aún prefiero usar la segunda forma. La razón principal es que es consistente con otros casos otra de captadores en los que sería tonto para llamar repetidamente, como una operación costosa:

if(person.expensiveOp() != null && person.expensiveOp().equalsIgnoreCase("Einstein")) { 
     method1(person.expensiveOp()); 
    } 
    method2(person.expensiveOp()); 
    method3(person.expensiveOp()); 
    method4(person.expensiveOp()); 

Además, con resaltado de sintaxis IDE, puedo aislar todo usos de un valor. Alguien responderá que también puede resaltar todos los usos del método. Pero puede haber múltiples llamadas al mismo método en diferentes instancias del objeto (es decir,toString())

0

Hice algunas investigaciones sobre este tema, después de hacer la pregunta y encontré lo que estaba buscando. El problema fue que no utilicé la terminología correcta al encuadrar la pregunta. 'Inline Expansion' (Compilación Inline) es lo que estaba buscando. He aquí una cita de wiki:

En informática, expansión en línea o inlining, es un manual o compilador optimización que reemplaza a un sitio de llamada de función con el cuerpo de la destinatario de la llamada. Esta optimización puede mejorar el uso de tiempo y espacio en tiempo de ejecución, , con el posible costo de aumentar el tamaño final del programa (es decir, el tamaño del archivo binario).

También encontré que este tema ya ha sido discutido en este sitio:

1) Do getters and setters impact performance in C++/D/Java?

He aquí una cita desde el enlace anterior:

En Java, el JIT el compilador probablemente lo alineará tarde o temprano. Como hasta donde yo sé, el compilador JVM JIT solo optimiza el código muy usado, , por lo que podría ver la sobrecarga de llamada de función inicialmente, hasta que getter/setter se ha llamado con demasiada frecuencia.

2) Inlining In Java

Cuestiones relacionadas