java que tan caro es una llamada a un método

81

Soy un principiante y siempre he leído que es malo repetir código. Sin embargo, parece que para no hacerlo, normalmente tendría que tener llamadas a métodos adicionales. Digamos que tengo la siguiente clase

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

¿Sería más óptimo para mí simplemente copiar y pegar las dos líneas en mi método clear () en el constructor en lugar de llamar al método real? Si es así, ¿qué diferencia hay? ¿Qué pasa si mi constructor realiza 10 llamadas a métodos y cada una simplemente establece una variable de instancia en un valor? ¿Cuál es la mejor práctica de programación?

jhlu87
fuente
4
Pero espere, si invoca el método ahora, lanzaremos una SEGUNDA llamada al método, ¡absolutamente gratis! ¡Pague solo el envío y el manejo! Hablando en serio. Hay sobrecarga en las llamadas a métodos, así como hay sobrecarga en tener más código para cargar. En algún momento, uno se vuelve más caro que el otro. La única forma de saberlo es comparando su código.
Marc B
11
cotizaciones de optimización prematura en 3 ... 2 ... 1
21
No veo el motivo del voto negativo sobre esto: el tipo está haciendo una pregunta perfectamente legítima. Puede que sea una respuesta obvia para algunos, ¡pero eso no la convierte en una mala pregunta!
Michael Berry
3
De hecho, es una pregunta muy legítima, la única razón para una votación en contra podría ser si hay un duplicado exacto.
Arafangion
1
Sí, lo siento si es obvio, pero estoy aprendiendo por mí mismo y solo lo he hecho durante unos meses. Algunas cosas que he visto en el código de muestra en línea no son una buena práctica, así que solo quiero verificarlas.
jhlu87

Respuestas:

74

¿Sería más óptimo para mí simplemente copiar y pegar las dos líneas en mi método clear () en el constructor en lugar de llamar al método real?

El compilador puede realizar esa optimización. Y también la JVM. La terminología utilizada por el redactor del compilador y los autores de JVM es "expansión en línea".

Si es así, ¿qué diferencia hay?

Mídelo. A menudo, encontrará que no hay ninguna diferencia. Y si cree que se trata de un punto de acceso de rendimiento, está buscando en el lugar equivocado; es por eso que necesitarás medirlo.

¿Qué pasa si mi constructor realiza 10 llamadas a métodos y cada una simplemente establece una variable de instancia en un valor?

De nuevo, eso depende del código de bytes generado y de las optimizaciones de tiempo de ejecución realizadas por la máquina virtual Java. Si el compilador / JVM puede incorporar las llamadas al método, realizará la optimización para evitar la sobrecarga de crear nuevos marcos de pila en tiempo de ejecución.

¿Cuál es la mejor práctica de programación?

Evitando la optimización prematura. La mejor práctica es escribir código legible y bien diseñado, y luego optimizar para los puntos calientes de rendimiento en su aplicación.

Vineet Reynolds
fuente
¿Cuál es una buena forma de comparar? ¿Hay algún software que pueda descargar o simplemente usa System.nanoTime () al principio y al final e imprime la diferencia?
jhlu87
System.nanoTime()o System.currentTimeMillises una mala forma de elaborar perfiles. Puede obtener una lista de perfiladores de la respuesta a esta pregunta de Stackoverflow . Recomendaría VisualVM, ya que ahora viene con el JDK.
Vineet Reynolds
2
@ jhlu87: Creo que le resultará muy difícil obtener una estimación precisa de la sobrecarga de una llamada a un método. El microbenchmarking es muy difícil de hacer bien e incluso entonces generalmente no es muy útil en el gran esquema de las cosas. Lea esto .
ColinD
@VineetReynolds la pregunta vinculada está muerta.
dim8
19

Lo que todos los demás han dicho sobre la optimización es absolutamente cierto.

No hay ninguna razón desde el punto de vista del rendimiento para incorporar el método. Si se trata de un problema de rendimiento, el JIT de su JVM lo integrará. En Java, las llamadas a métodos están tan cerca de ser gratuitas que no vale la pena pensar en ello.

Dicho esto, aquí hay un problema diferente. A saber, es mala práctica de programación para llamar a un método overrideable (es decir, uno que no lo es final, statico private) desde el constructor. (Efectivo Java, 2ª Ed., P. 89 en el ítem titulado "Diseñar y documentar para herencia o prohibirlo")

¿Qué sucede si alguien agrega una subclase de BinarySearchTreellamado LoggingBinarySearchTreeque anula todos los métodos públicos con un código como:

public void clear(){
  this.callLog.addCall("clear");
  super.clear();
}

¡Entonces LoggingBinarySearchTreenunca se podrá construir! El problema es que this.callLogserá nullcuando el BinarySearchTreeconstructor se esté ejecutando, pero el clearque se llama es el anulado y obtendrá un NullPointerException.

Tenga en cuenta que Java y C ++ difieren aquí: en C ++, un constructor de superclase que llama a un virtualmétodo termina llamando al definido en la superclase, no al anulado. Las personas que cambian entre los dos idiomas a veces lo olvidan.

Dado eso, creo que probablemente sea más limpio en su caso incorporar el clearmétodo cuando se llama desde el constructor , pero en general en Java debe seguir adelante y hacer todas las llamadas al método que desee.

Daniel Martín
fuente
1
No creo que estuviera pidiendo consejos de estilo de codificación, sino más bien saber si una llamada a un método es costosa o no
Asaf Mesika
3
Preguntó explícitamente "¿Cuál es la mejor práctica de programación?" - como una cuestión de mejores prácticas, esto es perfectamente relevante.
Daniel Martin
2
Si toma solo la última oración, pierde completamente el contexto de esta pregunta. Quiere saber si es costoso dividir su método grande en muchos métodos más pequeños, porque una llamada a un método tiene un precio. Agregar una respuesta que describa un patrón anti para llamar a un método no final de un constructor no cuenta como respuesta a su pregunta en su conjunto. Ah, y mira el título de la pregunta "¿Qué tan caro es una llamada de método?"
Asaf Mesika
6

Definitivamente lo dejaría como está. ¿Y si cambia la clear()lógica? No sería práctico encontrar todos los lugares donde copió las 2 líneas de código.

Marcelo
fuente
4

En términos generales (¡y como principiante esto significa siempre!), Nunca debes hacer microoptimizaciones como la que estás considerando. Siempre favorezca la legibilidad del código sobre cosas como esta.

¿Por qué? Porque el compilador / hotspot hará este tipo de optimizaciones sobre la marcha, y muchas, muchas más. En todo caso, cuando intente hacer optimizaciones a lo largo de este tipo de líneas (aunque no en este caso) probablemente hará las cosas más lentas. Hotspot entiende los modismos de programación comunes, si intenta hacer esa optimización usted mismo, probablemente no entenderá lo que está tratando de hacer, por lo que no podrá optimizarlo.

También hay un costo de mantenimiento mucho mayor. Si comienza a repetir el código, será mucho más difícil mantenerlo, ¡lo que probablemente será mucho más complicado de lo que cree!

Por otro lado, es posible que llegue a algunos puntos en su vida de codificación en los que necesite realizar optimizaciones de bajo nivel, pero si llega a esos puntos, definitivamente, definitivamente sabrá cuando llegue el momento. Y si no lo hace, siempre puede volver atrás y optimizar más tarde si es necesario.

Michael Berry
fuente
3

La mejor práctica es medir dos veces y cortar una vez.

Una vez que haya perdido el tiempo en la optimización, ¡nunca podrá recuperarla! (Así que primero mídelo y pregúntate si vale la pena optimizarlo. ¿Cuánto tiempo real ahorrarás?)

En este caso, la máquina virtual Java probablemente ya esté realizando la optimización de la que está hablando.

Arafangion
fuente
3

El costo de una llamada a un método es la creación (y eliminación) de un marco de pila y algunas expresiones de código de bytes adicionales si necesita pasar valores al método.

Andreas Dolk
fuente
1

El patrón que sigo es si este método en cuestión satisfaría o no uno de los siguientes:

  • ¿Sería útil tener este método disponible fuera de esta clase?
  • ¿Sería útil tener este método disponible en otros métodos?
  • ¿Sería frustrante reescribir esto cada vez que lo necesite?
  • ¿Podría aumentarse la versatilidad del método con el uso de algunos parámetros?

Si algo de lo anterior es cierto, debe incluirse en su propio método.

Melocotones491
fuente
¡Es más fácil no hacer estas preguntas y simplemente poner el maldito código en su propio método!
Arafangion
2
El autor de la pregunta tiene curiosidad sobre la granularidad de las llamadas a sus métodos. No es necesario crear un método para incrementar un número entero si solo puede usarloi++;
Peaches491
En realidad, la creación de un método tiene mucho valor, incluso si no existe la posibilidad de que se vuelva a utilizar. Simplemente dar un nombre a un bloque de código y hacer que aparezca la estructura general es un gran beneficio en sí mismo.
Joffrey
1

Conserve el clear()método cuando ayude a la legibilidad. Tener un código que no se puede mantener es más caro.

Fabián Barney
fuente
1

La optimización de los compiladores suele hacer un buen trabajo al eliminar la redundancia de estas operaciones "extra"; en muchos casos, la diferencia entre el código "optimizado" y el código simplemente escrito de la forma deseada y ejecutado a través de un compilador optimizador es nula; es decir, el compilador de optimización normalmente hace un trabajo tan bueno como tú, y lo hace sin causar ninguna degradación del código fuente. De hecho, muchas veces, el código "optimizado a mano" termina siendo MENOS eficiente, porque el compilador considera muchas cosas al hacer la optimización. Deje su código en un formato legible y no se preocupe por la optimización hasta más adelante.

"La optimización temprana es la raíz de todo mal." - Donald Knuth

Paul Sonier
fuente
0

No me preocuparía tanto por la llamada al método sino por la lógica del método. Si se tratara de sistemas críticos y el sistema necesitara "ser rápido", entonces buscaría optimizar los códigos que tardan mucho en ejecutarse.

Buhake Sindi
fuente
0

Dada la memoria de las computadoras modernas, esto es muy económico. Siempre es mejor dividir el código en métodos para que alguien pueda leer rápidamente lo que está pasando. También ayudará a reducir los errores en el código si el error se limita a un solo método con un cuerpo de unas pocas líneas.

Adamjmarkham
fuente
0

Como han dicho otros, el costo de la llamada al método es trivial a nada, ya que el compilador lo optimizará para usted.

Dicho esto, existen peligros al realizar llamadas a métodos a métodos de instancia desde un constructor. Corre el riesgo de actualizar posteriormente el método de instancia para que pueda intentar utilizar una variable de instancia que aún no haya sido iniciada por el constructor. Es decir, no necesariamente desea separar las actividades de construcción del constructor.

Otra pregunta: su método clear () establece la raíz en EMPTY, que se inicializa cuando se crea el objeto. Si luego agrega nodos a EMPTY y luego llama a clear (), no restablecerá el nodo raíz. ¿Es este el comportamiento que quieres?

Matthew Flynn
fuente