studio programacion para móviles libro edición desarrollo curso aplicaciones java performance premature-optimization

programacion - ¿Las llamadas estáticas de Java son más o menos costosas que las llamadas no estáticas?



manual de programacion android pdf (12)

Bueno, las llamadas estáticas no se pueden anular (por lo que siempre son candidatas para enlining), y no requieren ningún control de nulidad. HotSpot hace un montón de optimizaciones geniales, por ejemplo, métodos que pueden anular estas ventajas, pero son posibles razones por las que una llamada estática puede ser más rápida.

Sin embargo, eso no debería afectar su código de diseño de la manera más legible y natural, y solo preocúpese por este tipo de micro-optimización si tiene una causa justa (que casi nunca lo hará).

¿Hay algún beneficio de rendimiento de una forma u otra? ¿Es compilador / VM específico? Estoy usando Hotspot.


Como han dicho los carteles anteriores: esto parece una optimización prematura.

Sin embargo, hay una diferencia (una parte del hecho de que las invocaciones no estáticas requieren un empuje adicional de un objeto llamado en la pila de operandos):

Dado que los métodos estáticos no se pueden anular, no habrá búsquedas virtuales en el tiempo de ejecución para una llamada de método estático. Esto puede dar lugar a una diferencia observable en algunas circunstancias.

La diferencia en un nivel de código de bytes es que una llamada a un método no estático se realiza a través de INVOKEVIRTUAL , INVOKEINTERFACE o INVOKESPECIAL mientras que una llamada de método estático se realiza a través de INVOKESTATIC .


Como señala Jon, los métodos estáticos no se pueden anular, por lo que simplemente invocar un método estático puede ser, en un tiempo de ejecución de Java suficientemente ingenuo, más rápido que invocar un método de instancia.

Pero luego, incluso suponiendo que estás en el punto en el que te importa desordenar tu diseño para ahorrar unos nanosegundos, eso hace surgir otra pregunta: ¿vas a necesitar un método que te anule? Si cambias tu código para convertir un método de instancia en un método estático para guardar un nanosegundo aquí y allá, y luego das la vuelta e implementa tu propio despachador además de eso, es casi seguro que el tuyo será menos eficiente que el que construiste en tu tiempo de ejecución de Java ya.


En teoría, menos costoso.

La inicialización estática se realizará incluso si crea una instancia del objeto, mientras que los métodos estáticos no harán ninguna inicialización normalmente realizada en un constructor.

Sin embargo, no he probado esto.


Es compilador / VM específico.

  • En teoría , una llamada estática puede hacerse un poco más eficiente porque no necesita hacer una búsqueda de función virtual, y también puede evitar la sobrecarga del parámetro oculto "this".
  • En la práctica , muchos compiladores optimizarán esto de todos modos.

Por lo tanto, probablemente no valga la pena preocuparse a menos que haya identificado esto como un problema de rendimiento realmente crítico en su aplicación. La optimización prematura es la raíz de todo mal, etc.

Sin embargo, he visto que esta optimización proporciona un aumento sustancial en el rendimiento en la siguiente situación:

  • Método que realiza un cálculo matemático muy simple sin acceso a la memoria
  • Método invocado millones de veces por segundo en un circuito interno cerrado
  • Aplicación encuadernada a la CPU donde cada bit de rendimiento importaba

Si lo anterior se aplica a usted, puede valer la pena probarlo.

También hay otra razón buena (y potencialmente incluso más importante) para usar un método estático: si el método tiene realmente semántica estática (es decir, lógicamente no está conectado a una instancia determinada de la clase), entonces tiene sentido hacerlo estático para reflejar este hecho Los programadores experimentados de Java notarán el modificador estático e inmediatamente pensarán "¡ah !, este método es estático por lo que no necesita una instancia y presumiblemente no manipula el estado específico de la instancia". Entonces, habrás comunicado la naturaleza estática del método de forma efectiva ...


Es increíblemente improbable que cualquier diferencia en el rendimiento de las llamadas estáticas frente a las no estáticas marque la diferencia en su aplicación. Recuerde que "la optimización prematura es la raíz de todo mal".


Me gustaría agregar a las otras excelentes respuestas aquí que también depende de su flujo, por ejemplo:

Public class MyDao { private String sql = "select * from MY_ITEM"; public List<MyItem> getAllItems() { springJdbcTemplate.query(sql, new MyRowMapper()); }; };

Preste atención que crea un nuevo objeto MyRowMapper por cada llamada.
En cambio, sugiero usar aquí un campo estático.

Public class MyDao { private static RowMapper myRowMapper = new MyRowMapper(); private String sql = "select * from MY_ITEM"; public List<MyItem> getAllItems() { springJdbcTemplate.query(sql, myRowMapper); }; };


Para la decisión de si un método debe ser estático, el aspecto del rendimiento debería ser irrelevante. Si tiene un problema de rendimiento, hacer muchos métodos estáticos no va a salvar el día. Dicho esto, los métodos estáticos casi con certeza no son más lentos que cualquier método de instancia, en la mayoría de los casos marginalmente más rápido :

1.) Los métodos estáticos no son polimórficos, por lo que la JVM tiene que tomar menos decisiones para encontrar el código real para ejecutar. Este es un punto discutible en Age of Hotspot, ya que Hotspot optimizará las llamadas a métodos de instancia que tienen solo un sitio de implementación, por lo que realizarán lo mismo.

2.) Otra sutil diferencia es que los métodos estáticos obviamente no tienen "esta" referencia. Esto da como resultado una trama de pila una ranura más pequeña que la de un método de instancia con la misma firma y cuerpo ("esto" se coloca en la ranura 0 de las variables locales en el nivel de bytecode, mientras que para métodos estáticos la ranura 0 se usa para la primera parámetro del método).


Primero: no debe elegir entre estático y no estático en función del rendimiento.

Segundo: en la práctica, no hará ninguna diferencia. El punto de acceso puede optar por optimizar de forma que las llamadas estáticas sean más rápidas para un método, las llamadas no estáticas más rápidas para otro.

Tercero: gran parte de los mitos que rodean estática versus no estática se basan en JVM muy antiguas (que no se acercaban a la optimización que tiene Hotspot) o en trivialidades recordadas sobre C ++ (en las que una llamada dinámica usa un acceso de memoria más que una llamada estática).


Puede haber una diferencia, y podría ser de cualquier forma para un fragmento de código en particular, y podría cambiar incluso con un lanzamiento menor de la JVM.

Esto definitivamente forma parte del shreevatsa.wordpress.com/2008/05/16/… .


7 años después ...

No tengo un alto grado de confianza en los resultados que Mike Nakis encontró porque no abordan algunos problemas comunes relacionados con la optimización de Hotspot. He instrumentado benchmarks utilizando JMH y encontré que la sobrecarga de un método de instancia es de aproximadamente 0,75% en mi máquina frente a una llamada estática. Dado que los gastos generales son bajos, creo que, excepto en las operaciones más sensibles a la latencia, no es la mayor preocupación en el diseño de aplicaciones. Los resultados del resumen de mi punto de referencia de JMH son los siguientes;

java -jar target/benchmark.jar # -- snip -- Benchmark Mode Cnt Score Error Units MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s

Puedes mirar el código aquí en Github;

https://github.com/nfisher/svsi

El punto de referencia en sí es bastante simple, pero tiene como objetivo minimizar la eliminación del código muerto y el doblado constante. Posiblemente haya otras optimizaciones que he omitido o pasado por alto y es probable que estos resultados varíen según la versión de JVM y el sistema operativo.

package ca.junctionbox.svsi; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.infra.Blackhole; class InstanceSum { public int sum(final int a, final int b) { return a + b; } } class StaticSum { public static int sum(final int a, final int b) { return a + b; } } public class MyBenchmark { private static final InstanceSum impl = new InstanceSum(); @State(Scope.Thread) public static class Input { public int a = 1; public int b = 2; } @Benchmark public void testStaticMethod(Input i, Blackhole blackhole) { int sum = StaticSum.sum(i.a, i.b); blackhole.consume(sum); } @Benchmark public void testInstanceMethod(Input i, Blackhole blackhole) { int sum = impl.sum(i.a, i.b); blackhole.consume(sum); } }


Cuatro años después...

De acuerdo, con la esperanza de resolver esta cuestión de una vez y para siempre, he escrito un punto de referencia que muestra cómo los diferentes tipos de llamadas (virtuales, no virtuales, estáticas) se comparan entre sí.

Lo ejecuté en ideone , y esto es lo que obtuve:

(Mayor cantidad de iteraciones es mejor)

Success time: 3.12 memory: 320576 signal:0 Name | Iterations VirtualTest | 128009996 NonVirtualTest | 301765679 StaticTest | 352298601 Done.

Como se esperaba, las llamadas a métodos virtuales son las más lentas, las llamadas a métodos no virtuales son más rápidas y las llamadas a métodos estáticos son aún más rápidas.

Lo que no esperaba era que las diferencias fueran tan pronunciadas: las llamadas a métodos virtuales se midieron para funcionar a menos de la mitad de la velocidad de las llamadas a métodos no virtuales, que a su vez se midieron para ejecutar un 15% más lento que las llamadas estáticas. Eso es lo que muestran estas mediciones; las diferencias reales de hecho deben ser un poco más pronunciadas, ya que para cada llamada de método virtual, no virtual y estático, mi código de evaluación comparativa tiene una sobrecarga constante adicional de incrementar una variable entera, verificar una variable booleana y bucle si no es verdadera.

Supongo que los resultados variarán de CPU a CPU, y de JVM a JVM, así que pruébalo y mira lo que obtienes:

import java.io.*; class StaticVsInstanceBenchmark { public static void main( String[] args ) throws Exception { StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark(); program.run(); } static final int DURATION = 1000; public void run() throws Exception { doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ), new NonVirtualTest( new ClassWithNonVirtualMethod() ), new StaticTest() ); } void doBenchmark( Test... tests ) throws Exception { System.out.println( " Name | Iterations" ); doBenchmark2( devNull, 1, tests ); //warmup doBenchmark2( System.out, DURATION, tests ); System.out.println( "Done." ); } void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception { for( Test test : tests ) { long iterations = runTest( duration, test ); printStream.printf( "%15s | %10d/n", test.getClass().getSimpleName(), iterations ); } } long runTest( int duration, Test test ) throws Exception { test.terminate = false; test.count = 0; Thread thread = new Thread( test ); thread.start(); Thread.sleep( duration ); test.terminate = true; thread.join(); return test.count; } static abstract class Test implements Runnable { boolean terminate = false; long count = 0; } static class ClassWithStaticStuff { static int staticDummy; static void staticMethod() { staticDummy++; } } static class StaticTest extends Test { @Override public void run() { for( count = 0; !terminate; count++ ) { ClassWithStaticStuff.staticMethod(); } } } static class ClassWithVirtualMethod implements Runnable { int instanceDummy; @Override public void run() { instanceDummy++; } } static class VirtualTest extends Test { final Runnable runnable; VirtualTest( Runnable runnable ) { this.runnable = runnable; } @Override public void run() { for( count = 0; !terminate; count++ ) { runnable.run(); } } } static class ClassWithNonVirtualMethod { int instanceDummy; final void nonVirtualMethod() { instanceDummy++; } } static class NonVirtualTest extends Test { final ClassWithNonVirtualMethod objectWithNonVirtualMethod; NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod ) { this.objectWithNonVirtualMethod = objectWithNonVirtualMethod; } @Override public void run() { for( count = 0; !terminate; count++ ) { objectWithNonVirtualMethod.nonVirtualMethod(); } } } static final PrintStream devNull = new PrintStream( new OutputStream() { public void write(int b) {} } ); }

Vale la pena señalar que esta diferencia de rendimiento solo se aplica al código que no hace más que invocar métodos sin parámetros. Cualquier otro código que tenga entre las invocaciones diluirá las diferencias, y esto incluye el paso de parámetros. En realidad, la diferencia del 15% entre llamadas estáticas y no virtuales probablemente se explica en su totalidad por el hecho de que this puntero no tiene que pasarse al método estático. Por lo tanto, solo tomaría una cantidad bastante pequeña de código haciendo cosas triviales entre llamadas para diluir la diferencia entre diferentes tipos de llamadas hasta el punto de no tener impacto neto alguno.

Además, existen llamadas a métodos virtuales por una razón; tienen un propósito para servir, y se implementan utilizando los medios más eficientes proporcionados por el hardware subyacente. (El conjunto de instrucciones de la CPU.) Si, en su deseo de eliminarlos reemplazándolos con llamadas no virtuales o estáticas, termina teniendo que agregar tanto como un ápice de código adicional para emular su funcionalidad, entonces la sobrecarga neta resultante está vinculada no ser menos, sino más. Muy posiblemente, mucho, mucho, insondablemente mucho, más.