¿El uso de la palabra clave final en Java mejora el rendimiento?

347

En Java vemos muchos lugares donde el final se puede usar palabra clave pero su uso es poco común.

Por ejemplo:

String str = "abc";
System.out.println(str);

En el caso anterior, strpuede ser, finalpero esto comúnmente se deja fuera.

Cuando un método nunca se va a anular, podemos usar la palabra clave final. Del mismo modo, en el caso de una clase que no se va a heredar.

¿El uso de la palabra clave final en alguno o en todos estos casos realmente mejora el rendimiento? Si es así, ¿cómo? Por favor explique. Si el uso adecuado de finalrealmente es importante para el rendimiento, ¿qué hábitos debe desarrollar un programador de Java para hacer un mejor uso de la palabra clave?

Abhishek Jain
fuente
Yo no lo creo PAL, el método de despacho (de memorias caché de llamadas y ...) es un problema en los lenguajes dinámicos no en lenguajes de tipo estático
Jahan
Si ejecuto mi herramienta PMD (plugin para eclipse) utilizada para fines de revisión, sugiere hacer cambios para la variable en el caso como se muestra arriba. Pero no entendí su concepto. ¿Realmente el rendimiento golpea tanto?
Abhishek Jain
55
Pensé que esta era una pregunta de examen típica. Recuerdo aquella final hace tener influencia en el rendimiento, IIRC clases finales pueden ser optimizados por el JRE de alguna manera, porque no pueden tener subclases.
Kawu
Realmente hice esto probado. En todas las JVM He probado el uso de final sobre las variables locales hicieron mejorar el rendimiento (un poco, pero sin embargo puedo ser un factor en métodos de utilidad). El código fuente está en mi respuesta a continuación.
rustyx
1
"La optimización temprana es la raíz de todo mal". Solo deja que el compilador haga su trabajo. Escribir código legible y bien comentado. ¡Esa es siempre la mejor opción!
kaiser

Respuestas:

285

Usualmente no. Para los métodos virtuales, HotSpot realiza un seguimiento de si el método realmente se ha anulado y puede realizar optimizaciones, como la inclusión en el supuesto de que un método no se ha anulado, hasta que carga una clase que anula el método, en ese momento Puede deshacer (o deshacer parcialmente) esas optimizaciones.

(Por supuesto, esto supone que está utilizando HotSpot, pero es, con mucho, la JVM más común, así que ...)

En mi opinión, debe usarlo con finalbase en un diseño claro y legibilidad en lugar de por razones de rendimiento. Si desea cambiar algo por razones de rendimiento, debe realizar las medidas adecuadas antes de deformar el código más claro, de esa manera puede decidir si un rendimiento adicional alcanzado vale la legibilidad / diseño más pobre. (En mi experiencia, casi nunca vale la pena; YMMV).

EDITAR: Como se han mencionado los campos finales, vale la pena mencionar que a menudo son una buena idea de todos modos, en términos de diseño claro. También cambian el comportamiento garantizado en términos de visibilidad de hilos cruzados: después de que un constructor se ha completado, se garantiza que cualquier campo final sea visible en otros hilos inmediatamente. Este es probablemente el uso más común finalen mi experiencia, aunque como partidario de la regla empírica "diseñar para heredar o prohibir" de Josh Bloch, probablemente debería usarlo finalmás a menudo para las clases ...

Jon Skeet
fuente
1
@Abhishek: ¿Sobre qué en particular? El punto más importante es el último: es casi seguro que no debería preocuparse por esto.
Jon Skeet el
99
@Abishek: finalgeneralmente se recomienda porque hace que el código sea más fácil de entender y ayuda a encontrar errores (porque hace explícita la intención de los programadores). PMD probablemente recomienda usarlo finaldebido a estos problemas de estilo, no por razones de rendimiento.
sleske
3
@Abhishek: es probable que muchos de ellos sean específicos de JVM y que dependan de aspectos muy sutiles del contexto. Por ejemplo, creo que el servidor JVM de HotSpot aún permitirá la incorporación de métodos virtuales cuando se reemplaza en una clase, con una verificación de tipo rápida cuando corresponda. Pero los detalles son difíciles de precisar y bien pueden cambiar entre los publicados.
Jon Skeet
55
Aquí me gustaría citar Effective Java, 2ª edición, artículo 15, Minimizar la mutabilidad: Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.. Además An immutable object can be in exactly one state, the state in which it was created.vs Mutable objects, on the other hand, can have arbitrarily complex state spaces.. Desde mi experiencia personal, el uso de la palabra clave finaldebería resaltar la intención del desarrollador de inclinarse hacia la inmutabilidad, no para "optimizar" el código. ¡Te animo a leer este capítulo, fascinante!
Louis F.
2
Otras respuestas muestran que el uso de la finalpalabra clave en las variables podría reducir la cantidad de bytecode, lo que puede producir un efecto en el rendimiento.
Julien Kronegg
86

Respuesta corta: ¡no te preocupes por eso!

Respuesta larga:

Al hablar sobre las variables locales finales, tenga en cuenta que el uso de la palabra clave finalayudará al compilador a optimizar el código estáticamente , lo que al final puede resultar en un código más rápido. Por ejemplo, las cadenas finales a + ben el ejemplo a continuación se concatenan estáticamente (en tiempo de compilación).

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

¿El resultado?

Method with finals took 5 ms
Method without finals took 273 ms

Probado en Java Hotspot VM 1.7.0_45-b18.

Entonces, ¿cuánto es la mejora del rendimiento real? No me atrevo a decir En la mayoría de los casos, probablemente marginal (~ 270 nanosegundos en esta prueba sintética porque la concatenación de cadenas se evita por completo, un caso raro), pero en un código de utilidad altamente optimizado podría ser un factor. En cualquier caso, la respuesta a la pregunta original es sí, podría mejorar el rendimiento, pero marginalmente en el mejor de los casos .

Dejando de lado los beneficios en tiempo de compilación, no pude encontrar ninguna evidencia de que el uso de la palabra clave finaltenga un efecto medible en el rendimiento.

rustyx
fuente
2
Reescribí su código un poco para probar ambos casos 100 veces. Finalmente, el tiempo promedio de la final fue de 0 ms y 9 ms para la final. Aumentar el recuento de iteraciones a 10M establece el promedio en 0 ms y 75 ms. Sin embargo, la mejor carrera para la final no fue de 0 ms. ¿Tal vez es la VM que detecta los resultados que simplemente se están tirando o algo así? No sé, pero independientemente, el uso de final hace una diferencia significativa.
Casper Færgemand
55
Prueba defectuosa. Las pruebas anteriores calentarán la JVM y beneficiarán las invocaciones de pruebas posteriores. Reordenar sus pruebas y ver qué pasa. Debe ejecutar cada prueba en su propia instancia de JVM.
Steve Kuo
16
No, la prueba no tiene fallas, se tuvo en cuenta el calentamiento. La segunda prueba es MÁS LENTO, no más rápido. Sin calentamiento, la segunda prueba sería INCLUSO MÁS LENTO.
rustyx
66
En testFinal () todo el tiempo devolvió el mismo objeto, desde el conjunto de cadenas, porque la resistencia de las cadenas finales y la concatenación de cadenas literales se evalúan en tiempo de compilación. testNonFinal () cada vez que devuelve un nuevo objeto, eso explica la diferencia de velocidad.
anber
44
¿Qué te hace pensar que el escenario no es realista? Stringla concatenación es una operación mucho más costosa que sumar Integers. Hacerlo estáticamente (si es posible) es más eficiente, eso es lo que muestra la prueba.
rustyx
62

Sí puede. Aquí hay una instancia donde final puede aumentar el rendimiento:

La compilación condicional es una técnica en la que las líneas de código no se compilan en el archivo de clase en función de una condición particular. Esto se puede usar para eliminar toneladas de código de depuración en una compilación de producción.

considera lo siguiente:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (doSomething) {
       // do first part. 
    }

    if (doSomething) {
     // do second part. 
    }

    if (doSomething) {     
      // do third part. 
    }

    if (doSomething) {
    // do finalization part. 
    }
}

Al convertir el atributo doSomething en un atributo final, le ha dicho al compilador que cada vez que vea doSomething, debe reemplazarlo por falso según las reglas de sustitución en tiempo de compilación. El primer paso del compilador cambia el código a algo como esto:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (false){
       // do first part. 
    }

    if (false){
     // do second part. 
    }

    if (false){
      // do third part. 
    }

    if (false){
    // do finalization part. 

    }
}

Una vez hecho esto, el compilador vuelve a mirarlo y ve que hay declaraciones inalcanzables en el código. Como está trabajando con un compilador de alta calidad, no le gustan todos esos códigos de bytes inalcanzables. Entonces los elimina y terminas con esto:

public class ConditionalCompile {


  private final static boolean doSomething= false;

  public static void someMethodBetter( ) {

    // do first part. 

    // do second part. 

    // do third part. 

    // do finalization part. 

  }
}

reduciendo así los códigos excesivos o cualquier verificación condicional innecesaria.

Editar: como ejemplo, tomemos el siguiente código:

public class Test {
    public static final void main(String[] args) {
        boolean x = false;
        if (x) {
            System.out.println("x");
        }
        final boolean y = false;
        if (y) {
            System.out.println("y");
        }
        if (false) {
            System.out.println("z");
        }
    }
}

Al compilar este código con Java 8 y descompilarlo javap -c Test.classobtenemos:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static final void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ifeq          14
       6: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #22                 // String x
      11: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: iconst_0
      15: istore_2
      16: return
}

Podemos notar que el código compilado incluye solo la variable no final x. Esto prueba que las variables finales tienen un impacto en el rendimiento, al menos para este caso simple.

mel3kings
fuente
1
@ ŁukaszLech Aprendí esto de un libro de Oreilly: Hardcore Java, en su capítulo sobre la palabra clave final.
mel3kings
15
Esto habla de la optimización en el momento de la compilación, significa que el desarrollador conoce el VALOR de la variable booleana final en el momento de la compilación, cuál es el punto de escribir si los bloques están en primer lugar, entonces para este escenario donde las CONDICIONES IF no son necesarias y no ¿cualquier sentido? En mi opinión, incluso si esto aumenta el rendimiento, este es un código incorrecto en primer lugar y puede ser optimizado por el propio desarrollador en lugar de pasar la responsabilidad al compilador, y la pregunta tiene la intención principal de preguntar sobre las mejoras de rendimiento en los códigos habituales donde se usa el final. Tiene sentido programático.
Bhavesh Agarwal
77
El objetivo de esto sería agregar declaraciones de depuración como se indica en mel3kings. Puede voltear la variable antes de una compilación de producción (o configurarla en sus scripts de compilación) y eliminar automáticamente todo ese código cuando se crea la distribución.
Adam
16

Estoy sorprendido de que nadie haya publicado un código real que se descompila para demostrar que hay al menos alguna diferencia menor.

Para la referencia de este ha sido probado contra javacversión 8, 9y 10.

Supongamos este método:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

Compilar este código tal como es, produce exactamente el mismo código de byte que cuando finalhubiera estado presente ( final Object left = new Object();).

Pero este:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

Produce:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

Dejar finalestar presente produce:

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

El código se explica por sí solo, en caso de que haya una constante de tiempo de compilación, se cargará directamente en la pila de operandos (no se almacenará en la matriz de variables locales como lo hace el ejemplo anterior bipush 12; istore_0; iload_0), lo que tiene sentido ya que nadie puede cambiarlo.

Por otro lado, por qué en el segundo caso el compilador no produce istore_0 ... iload_0está más allá de mí, no es que esa ranura 0se use de ninguna manera (podría reducir la matriz de variables de esta manera, pero es posible que me falten algunos detalles internos, no puedo decir con seguridad)

Me sorprendió ver tal optimización, considerando lo poco que javachacen los pequeños . ¿En cuanto a lo que debemos usar siempre final? Ni siquiera voy a escribir una JMHprueba (que quería inicialmente), estoy seguro de que la diferencia está en el orden de ns(si es posible que se capture). El único lugar en el que esto podría ser un problema es cuando un método no se puede alinear debido a su tamaño (y declararfinal reduciría ese tamaño en unos pocos bytes).

Hay dos finals más que deben abordarse. Primero es cuando un método es final(desde una JITperspectiva), tal método es monomórfico , y estos son los más queridos por el JVM.

Luego hay finalvariables de instancia (que deben establecerse en cada constructor); estos son importantes ya que garantizarán una referencia publicada correctamente, como se ha tocado aquí un poco y también se especifica exactamente por el JLS.

Eugene
fuente
Compilé el código usando Java 8 (JDK 1.8.0_162) con las opciones de depuración ( javac -g FinalTest.java), descompilé el código usando javap -c FinalTest.classy no final int left=12obtuve los mismos resultados (con , obtuve bipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn). Entonces sí, el bytecode generado depende de muchos factores y es difícil decir si tener finalo no producir un efecto en el rendimiento. Pero como el código de bytes es diferente, puede existir alguna diferencia de rendimiento.
Julien Kronegg
13

Realmente está preguntando acerca de dos (al menos) casos diferentes:

  1. final para variables locales
  2. final para métodos / clases

Jon Skeet ya ha respondido 2). Alrededor de 1):

No creo que haga la diferencia; Para las variables locales, el compilador puede deducir si la variable es final o no (simplemente comprobando si está asignada más de una vez). Entonces, si el compilador quería optimizar las variables que solo se asignan una vez, puede hacerlo sin importar si la variable se declara realmente finalo no.

final podría hacer una diferencia para los campos de clase protegida / pública; allí es muy difícil para el compilador averiguar si el campo se está configurando más de una vez, ya que podría suceder desde una clase diferente (que puede que ni siquiera se haya cargado). Pero incluso entonces, la JVM podría usar la técnica que Jon describe (optimice de manera optimista, revierta si se carga una clase que cambia el campo).

En resumen, no veo ninguna razón por la que debería ayudar al rendimiento. Por lo tanto, es poco probable que este tipo de microoptimización ayude. Podrías intentar compararlo para asegurarte, pero dudo que haga una diferencia.

Editar:

De hecho, según la respuesta de Timo Westkämper, en algunos casos final puede mejorar el rendimiento de los campos de clase. Estoy corregido.

sleske
fuente
No creo que el compilador pueda verificar correctamente la cantidad de veces que se asigna una variable local: ¿qué pasa con las estructuras if-then-else con numerosas asignaciones?
gyorgyabraham
1
@ gyabraham: el compilador ya comprueba estos casos si declara una variable local como final, para asegurarse de que no la asigna dos veces. Hasta donde puedo ver, la misma lógica de verificación se puede (y probablemente se usa) para verificar si una variable podría ser final.
sleske
La finalidad de una variable local no se expresa en el código de bytes, por lo que la JVM ni siquiera sabe que fue final
Steve Kuo
1
@SteveKuo: incluso si no está expresado en el código de bytes, puede ayudar javaca optimizar mejor. Pero esto es solo especulación.
sleske 05 de
1
El compilador podría averiguar si una variable local ha sido asignada exactamente una vez, pero en la práctica no lo hace (más allá de la comprobación de errores). Por otro lado, si una finalvariable es de un tipo o tipo primitivo Stringy se le asigna inmediatamente una constante de tiempo de compilación como en el ejemplo de la pregunta, el compilador debe alinearla, ya que la variable es una constante de tiempo de compilación por especificación. Pero para la mayoría de los casos de uso, el código puede tener un aspecto diferente, pero de todos modos no importa si la constante está en línea o se lee desde una variable local en términos de rendimiento.
Holger
6

Nota: no es un experto en Java

Si recuerdo mi Java correctamente, habría muy poca forma de mejorar el rendimiento con la palabra clave final. Siempre he sabido que existe para el "buen código": diseño y legibilidad.

Neowizard
fuente
1

No soy un experto, pero supongo que debe agregar una finalpalabra clave a la clase o método si no se sobrescribirá y dejar las variables solas. Si habrá alguna forma de optimizar tales cosas, el compilador lo hará por usted.

Crozin
fuente
1

En realidad, mientras probaba un código relacionado con OpenGL, descubrí que usar el modificador final en un campo privado puede degradar el rendimiento. Aquí está el comienzo de la clase que probé:

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

Y este es el método que utilicé para probar el rendimiento de varias alternativas, entre las cuales la clase ShaderInput:

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

Después de la tercera iteración, con la VM calentada, obtuve constantemente estas cifras sin la palabra clave final:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

Con la palabra clave final:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

Tenga en cuenta las cifras para la prueba ShaderInput.

No importaba si hacía los campos públicos o privados.

Por cierto, hay algunas cosas más desconcertantes. La clase ShaderInput supera a todas las demás variantes, incluso con la palabra clave final. Esto es notable b / c, básicamente es una clase que envuelve una matriz flotante, mientras que las otras pruebas manipulan directamente la matriz. Tengo que resolver esto. Puede tener algo que ver con la interfaz fluida de ShaderInput.

Además, System.arrayCopy aparentemente es algo más lento para matrices pequeñas que simplemente copiar elementos de una matriz a otra en un bucle for. Y el uso de sun.misc.Unsafe (así como el java.nio.FloatBuffer directo, que no se muestra aquí) funciona abismalmente.

usuario3663845
fuente
1
Olvidó hacer los parámetros finales. <pre> <code> public ShaderInput x (final int stride, final float val) {input [strides [stride] + 0] = val; devuelve esto; } </code> </pre> En mi experiencia, hacer cualquier variable o campo final puede de hecho aumentar el rendimiento.
Anticro
1
Ah, y también haga que los demás sean finales también: <pre> <code> final int arraySize = 10; flotante final [] fb = flotante nuevo [arraySize]; para (int i = 0; i <arraySize; i ++) {fb [i] = random.nextFloat (); } tiempos int finales = 1000000000; para (int i = 0; i <10; ++ i) {floatVectorTest (times, fb); arrayCopyTest (veces, fb); shaderInputTest (veces, fb); directFloatArrayTest (veces, fb); System.out.println (); System.gc (); } </code> </pre>
Anticro el
1

Final (al menos para las variables y parámetros miembros) es más para los humanos que para la máquina.

Es una buena práctica hacer que las variables sean finales siempre que sea posible. Desearía que Java hubiera hecho "variables" finales por defecto y tuviera una palabra clave "Mutable" para permitir cambios. Las clases inmutables conducen a un código de subprocesos mucho mejor, y solo mirar una clase con "final" delante de cada miembro mostrará rápidamente que es inmutable.

Otro caso: he estado convirtiendo una gran cantidad de código para usar anotaciones @ NonNull / @ Nullable (Puedes decir que un parámetro de método no debe ser nulo, entonces el IDE puede advertirte cada lugar que pasas una variable que no está etiquetada @ No nulo: todo se extiende hasta un grado ridículo). Es mucho más fácil demostrar que una variable miembro o parámetro no puede ser nulo cuando se etiqueta como final, ya que sabe que no se reasigna en ningún otro lugar.

Mi sugerencia es adquirir el hábito de aplicar el final para miembros y parámetros de forma predeterminada, solo son unos pocos caracteres, pero lo empujarán a mejorar su estilo de codificación si no es nada más.

Final para métodos o clases es otro concepto, ya que no permite una forma muy válida de reutilización y realmente no le dice mucho al lector. El mejor uso es probablemente la forma en que hicieron que String y los otros tipos intrínsecos sean definitivos para que pueda confiar en un comportamiento coherente en todas partes: eso evitó muchos errores (aunque a veces me ENCANTARÍA extender la cadena ... ¡oh! posibilidades)

Bill K
fuente
0

Como se mencionó en otra parte, 'final' para una variable local y, en menor medida, una variable miembro, es más una cuestión de estilo.

'final' es una declaración de que tiene la intención de que la variable no cambie (es decir, ¡la variable no variará!). El compilador puede ayudarlo quejándose si viola su propia restricción.

Comparto el sentimiento de que Java habría sido un mejor lenguaje si los identificadores (lo siento, simplemente no puedo llamar a una cosa que no varía como 'variable') fueran finales por defecto, y requiriera que dijeras explícitamente que eran variables. Pero dicho esto, generalmente no uso 'final' en variables locales que se inicializan y nunca se asignan; Simplemente parece demasiado ruidoso.

(Yo uso final en las variables miembro)

usuario13752845
fuente
-3

Los miembros declarados con final estarán disponibles en todo el programa porque, a diferencia de los no finales, si estos miembros no se han utilizado en el programa, Garbage Collector no los tendrá en cuenta, por lo que puede causar problemas de rendimiento debido a una mala gestión de la memoria.

nams
fuente
2
¿Qué clase de tontería es esta? ¿Tienes alguna fuente por qué crees que este es el caso?
J. Doe
-4

final La palabra clave se puede utilizar de cinco maneras en Java.

  1. Una clase es final
  2. Una variable de referencia es final
  3. Una variable local es final
  4. Un método es final.

Una clase es final: una clase es final significa que no podemos extendernos o herencia significa que la herencia no es posible.

Del mismo modo: un objeto es final: en algún momento no modificamos el estado interno del objeto, por lo que en ese caso podemos especificar que el objeto es final object.object final significa no variable también final.

Una vez que la variable de referencia se convierte en definitiva, no se puede reasignar a otro objeto. Pero puede cambiar el contenido del objeto siempre que sus campos no sean finales

adityaraj
fuente
2
Por "objeto es final" se quiere decir "objeto es inmutable".
gyorgyabraham
66
Tienes razón, pero no estás respondiendo la pregunta. OP no preguntó qué finalsignifica, pero si el uso finalinfluye en el rendimiento.
Honza Zidek