java performance getter-setter

Java: ¿Getter y setter más rápido que el acceso directo?



performance getter-setter (3)

En caso de que no haya tomado un curso de Estadística, siempre existe una variación en el rendimiento del programa, sin importar qué tan bien esté escrito. La razón por la que estos dos métodos parecen ejecutarse aproximadamente a la misma velocidad es porque los campos de acceso solo hacen una cosa: devuelven un campo en particular. Como no sucede nada más en el método de acceso, ambas tácticas hacen prácticamente lo mismo; sin embargo, en caso de que no sepa acerca de la encapsulación, que es lo bueno que un programador oculta los datos (campos o atributos) del usuario, una regla importante de la encapsulación es no revelar datos internos al usuario . Modificar un campo como público significa que cualquier otra clase puede acceder a esos campos, y eso puede ser muy peligroso para el usuario. Es por eso que siempre recomiendo que los programadores de Java utilicen métodos de acceso y mutación para que los campos no lleguen a las manos equivocadas.

En caso de que tuviera curiosidad acerca de cómo acceder a un campo privado, puede usar la reflexión, que en realidad accede a los datos de una clase en particular para que pueda mutar si realmente debe hacerlo. Como ejemplo frívolo, suponga que sabía que la clase java.lang.String contiene un campo privado de tipo char [] (es decir, una matriz char). Está oculto para el usuario, por lo que no puede acceder al campo directamente. (Por cierto, el método java.lang.String.toCharArray () accede al campo por usted). Si desea acceder a cada carácter de manera consecutiva y almacenar cada carácter en una colección (por simplicidad, ¿por qué no un java?). util.List?), entonces aquí es cómo usar la reflexión en este caso:

/** This method iterates through each character in a <code>String</code> and places each of them into a <code>java.util.List</code> of type <code>Character</code>. @param str The <code>String</code> to extract from. @param list The list to store each character into. (This is necessary because the compiler knows not which <code>List</code> to use, so it will automatically clear the list anyway.) */ public static void extractStringData(String str, List<Character> list) throws IllegalAccessException, NoSuchFieldException { java.lang.reflect.Field value = String.class.getDeclaredField("value"); value.setAccessible(true); char[] data = (char[]) value.get(str); for(char ch : data) list.add(ch); }

Como nota al margen, tenga en cuenta que la reflexión le quita mucho rendimiento a su programa. Si hay un campo, un método o una clase interna o anidada a la que debe acceder por cualquier motivo (lo cual es muy poco probable de todos modos), entonces debe usar la reflexión. La razón principal por la que la reflexión quita un desempeño precioso es debido a las excepciones innumerables que arroja. Me alegro de haber ayudado!

Probé el rendimiento de un trazador de rayos Java en el que estoy escribiendo con VisualVM 1.3.7 en mi Netbook de Linux. Medí con el perfilador.
Por diversión, probé si hay una diferencia entre usar getters y setters y acceder directamente a los campos. Los captadores y definidores son códigos estándar sin adición.

No esperaba ninguna diferencia. Pero el código de acceso directo fue más lento.

Aquí está la muestra que probé en Vector3D:

public float dot(Vector3D other) { return x * other.x + y * other.y + z * other.z; }

Tiempo: 1542 ms / 1,000,000 invocaciones.

public float dot(Vector3D other) { return getX() * other.getX() + getY() * other.getY() + getZ() * other.getZ(); }

Tiempo: 1453 ms / 1.000.000 invocaciones.

No lo probé en un micro-benchmark, sino en el trazador de rayos. La forma en que probé el código:

  • Comencé el programa con el primer código y lo configuré. El trazador de rayos no se está ejecutando todavía.
  • Comencé el perfilador y esperé un poco después de que se realizó la inicialización.
  • Comencé un trazador de rayos.
  • Cuando VisualVM mostró suficientes invocaciones, detuve el generador de perfiles y esperé un poco.
  • Cerré el programa de trazador de rayos.
  • Reemplacé el primer código con el segundo y repetí los pasos anteriores después de compilar.

Hice al menos 20,000,000 de invocaciones para ambos códigos. Cerré cualquier programa que no necesitaba. Puse mi CPU en rendimiento, por lo que el reloj de mi CPU estaba en máx. todo el tiempo.
¿Cómo es posible que el segundo código sea un 6% más rápido?


Gracias a todos por ayudarme a responder esta pregunta. Al final, encontré la respuesta.

Primero, Bohemian tiene razón : con PrintAssembly verifiqué la suposición de que los códigos de ensamblaje generados son idénticos. Y sí, aunque los códigos de bytes son diferentes, los códigos generados son idénticos.
Así que masterxilo tiene razón : el generador de perfiles tiene que ser el culpable. Pero la suposición de masterxilo acerca de las cercas de tiempo y más código de instrumentación no puede ser cierta; Ambos códigos son idénticos al final.

Así que todavía queda la pregunta: ¿Cómo es posible que el segundo código parezca ser un 6% más rápido en el generador de perfiles?

La respuesta está en la forma en que VisualVM mide: antes de comenzar a crear perfiles, necesita datos de calibración. Esto se utiliza para eliminar el tiempo de sobrecarga causado por el generador de perfiles.
Aunque los datos de calibración son correctos, el cálculo final de la medición no lo es. VisualVM ve las invocaciones del método en el bytecode. Pero no ve que el compilador JIT elimine estas invocaciones mientras optimiza.
Por lo tanto, elimina el tiempo de sobrecarga no existente. Y así es como aparece la diferencia.


Hice algunos micro benchmarking con un montón de calentamiento de JVM y descubrí que los dos enfoques requieren la misma cantidad de tiempo de ejecución .

Esto sucede porque el compilador JIT está alineando el método getter con un acceso directo al campo, lo que los convierte en un código de bytes idéntico.