2010-01-31 14 views
84

¿Hay alguna sobrecarga cuando lanzamos objetos de un tipo a otro? ¿O el compilador simplemente resuelve todo y no hay ningún costo en el tiempo de ejecución?¿La fundición de Java introduce gastos generales? ¿Por qué?

¿Se trata de un asunto general, o hay diferentes casos?

Por ejemplo, supongamos que tenemos una matriz de Objeto [], donde cada elemento puede tener un tipo diferente. Pero siempre sabemos con certeza que, por ejemplo, el elemento 0 es un doble, el elemento 1 es una cadena. (Sé que este es un diseño incorrecto, pero supongamos que tengo que hacer esto).

¿La información del tipo de Java aún se mantiene en tiempo de ejecución? O todo se olvida después de la compilación, y si hacemos (Doble) elementos [0], simplemente seguiremos el puntero e interpretaremos esos 8 bytes como un doble, sea lo que sea.

No tengo muy claro cómo se hacen los tipos en Java. Si tiene alguna recomendación sobre libros o artículos, entonces gracias también.

+0

El rendimiento de instanceof y casting es bastante bueno. Publiqué un momento en Java7 sobre diferentes enfoques del problema aquí: http://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it- throw-excep/28858680 # 28858680 – Wheezil

+0

Esta otra pregunta tiene muy buenas respuestas http://stackoverflow.com/questions/16741323/casting-performance-in-different-levels-when-casting-down – user454322

Respuesta

63

Hay 2 tipos de casting:

implícito de fundición, cuando lanzas de un tipo a otro tipo más amplio, que se realiza de forma automática y no hay ninguna sobrecarga:

String s = "Cast"; 
Object o = s; // implicit casting 

explícita fundición, cuando pasa de un tipo más ancho a uno más estrecho. Para este caso, de manera explícita debes utilizar la fundición de esa manera:

Object o = someObject; 
String s = (String) o; // explicit casting 

En este segundo caso, no hay sobrecarga en tiempo de ejecución, porque los dos tipos deben ser revisados ​​y en caso de que la fundición no es factible, JVM debe lanzar una ClassCastException.

Tomado de JavaWorld: The cost of casting

casting se utiliza para convertir entre tipos - entre los tipos de referencia en particular, para el tipo de fundición operación en la que estamos interesados ​​ aquí.

upCast operaciones (también llamados conversiones de ampliación de la especificación del lenguaje Java ) convertir una referencia subclase a una referencia de clase ancestro . Esta operación de fundición es normalmente automática, ya que es siempre segura y puede ser implementada directamente por el compilador.

Downcast operaciones (también llamados conversiones de restricción en el Java Language Specification) convertir una referencia de clase ancestro a una subclase referencia. Esta operación de conversión crea una sobrecarga de ejecución, ya que Java requiere que el molde se compruebe en el tiempo de ejecución para asegurarse de que sea válido. Si el objeto referenciado no es una instancia de sea el tipo de destino para el yeso o una subclase de ese tipo, el intento de yeso no se permite y debe lanzar una java.lang.ClassCastException .

+79

Ese artículo de JavaWorld tiene más de 10 años viejo, así que tomaría cualquier declaración que haga sobre el rendimiento con un grano muy grande de su mejor sal. – skaffman

+0

@skaffman, de hecho tomaría cualquier declaración que haga (independientemente del rendimiento de no) con un grano de sal. – Pacerier

+0

¿Será el mismo caso, si no asigno el objeto fundido a la referencia y solo invoco el método en él? como '((String) o) .someMethodOfCastedClass()' –

7

Por ejemplo, supongamos que tenemos una serie de Object [], donde cada elemento puede tener un tipo diferente. Pero siempre sabemos con certeza que, por ejemplo, el elemento 0 es un doble, el elemento 1 es una cadena. (Sé que este es un diseño incorrecto, pero supongamos que tengo que hacer esto).

El compilador no tiene en cuenta los tipos de los elementos individuales de una matriz. Simplemente comprueba que el tipo de expresión de cada elemento se puede asignar al tipo de elemento de matriz.

¿La información del tipo de Java todavía se conserva en el tiempo de ejecución? O todo se olvida después de la compilación, y si hacemos (Doble) elementos [0], simplemente seguiremos el puntero e interpretaremos esos 8 bytes como un doble, cualquiera que sea eso?

Se mantiene cierta información en tiempo de ejecución, pero no los tipos estáticos de los elementos individuales. Puedes ver esto al mirar el formato de archivo de la clase.

Es teóricamente posible que el compilador JIT pueda usar "análisis de escape" para eliminar verificaciones innecesarias de tipos en algunas asignaciones. Sin embargo, hacer esto en la medida en que sugiera estaría más allá de los límites de la optimización realista. La recompensa de analizar los tipos de elementos individuales sería demasiado pequeña.

Además, las personas no deberían escribir códigos de aplicaciones así de todos modos.

+0

¿Qué pasa con los primitivos? '(float) Math.toDegrees (theta)' ¿Habrá una sobrecarga significativa aquí también? –

+1

Hay una sobrecarga para algunos moldes primitivos. Si es significativo depende del contexto. –

5

Las instrucciones de código de bytes para realizar la conversión en tiempo de ejecución se llaman checkcast. Puede desensamblar código Java usando javap para ver qué instrucciones se generan.

Para las matrices, Java mantiene la información del tipo en tiempo de ejecución. La mayoría de las veces, el compilador detectará errores de tipo para usted, pero hay casos en los que se encontrará con un ArrayStoreException cuando intente almacenar un objeto en una matriz, pero el tipo no coincide (y el compilador no lo detectó)) El Java language spec da el siguiente ejemplo:

class Point { int x, y; } 
class ColoredPoint extends Point { int color; } 
class Test { 
    public static void main(String[] args) { 
     ColoredPoint[] cpa = new ColoredPoint[10]; 
     Point[] pa = cpa; 
     System.out.println(pa[1] == null); 
     try { 
      pa[0] = new Point(); 
     } catch (ArrayStoreException e) { 
      System.out.println(e); 
     } 
    } 
} 

Point[] pa = cpa es válido, ya que ColoredPoint es una subclase de punto, pero pa[0] = new Point() no es válido.

Esto se opone a los tipos genéricos, donde no hay información de tipo guardada en tiempo de ejecución. El compilador inserta instrucciones checkcast donde sea necesario.

Esta diferencia de tipeo para tipos genéricos y matrices a menudo hace que no sea adecuado mezclar matrices y tipos genéricos.

37

Para una aplicación razonable de Java:

Cada objeto tiene una cabecera que contiene, entre otras cosas, un puntero al tipo de tiempo de ejecución (por ejemplo Double o String, pero nunca podría haber CharSequence o AbstractList).Suponiendo que el compilador de tiempo de ejecución (generalmente HotSpot en el caso de Sun) no puede determinar el tipo de forma estática, el código de máquina generado debe realizar algunas comprobaciones.

Primero, debe leerse el puntero al tipo de tiempo de ejecución. Esto es necesario para llamar a un método virtual en una situación similar de todos modos.

Para convertir a un tipo de clase, se sabe exactamente cuántas superclases hay hasta que tocas java.lang.Object, por lo que el tipo se puede leer con una desviación constante del puntero de tipo (en realidad los primeros ocho en HotSpot). De nuevo, esto es análogo a leer un puntero de método para un método virtual.

Luego, el valor de lectura solo necesita una comparación con el tipo estático esperado del modelo. Dependiendo de la arquitectura del conjunto de instrucciones, otra instrucción necesitará derivarse (o fallar) en una rama incorrecta. Las ISA como ARM de 32 bits tienen instrucciones condicionales y pueden hacer que la ruta triste pase por la ruta feliz.

Las interfaces son más difíciles debido a la herencia múltiple de la interfaz. En general, los últimos dos moldes a las interfaces se almacenan en caché en el tipo de tiempo de ejecución. EN los primeros días (hace más de una década), las interfaces eran un poco lentas, pero eso ya no es relevante.

Con suerte, puede ver que este tipo de cosas es en gran medida irrelevante para el rendimiento. Tu código fuente es más importante. En términos de rendimiento, es probable que el mayor golpe en su escenario sea la falta de caché al perseguir punteros de objeto por todas partes (la información de tipo será, por supuesto, común).

+1

interesante - entonces esto significa que para clases que no son de interfaz si escribo Superclass sc = (Superclass) subclase; que el compilador (jit ie: load time) colocará "estáticamente" el desplazamiento de Object en cada una de Superclass y Subclass en sus encabezados "Class" y luego mediante un simple add + compare podrá resolver las cosas? - eso es bueno y rápido :) Para las interfaces, supongo que no es peor que una pequeña tabla hash o btree? – peterk

+0

@peterk Para la conversión entre clases, no cambian tanto la dirección del objeto como la "vtbl" (tabla de punteros del método, más la tabla de la jerarquía de clases, la memoria caché de la interfaz, etc.). Entonces, el elenco de [clase] verifica el tipo, y si no le queda nada más, tiene que suceder. –

1

En teoría, se introducen gastos generales. Sin embargo, las JVM modernas son inteligentes. Cada implementación es diferente, pero no es irrazonable suponer que podría existir una implementación que JIT optimizara las verificaciones de casting de distancia cuando podría garantizar que nunca habría un conflicto. En cuanto a las JVM específicas que ofrecen esto, no podría decírtelo. Debo admitir que me gustaría conocer los detalles de la optimización de JIT yo mismo, pero estos son para los ingenieros de JVM que preocuparse.

La moraleja de la historia es escribir primero un código comprensible. Si experimentas ralentizaciones, perfila e identifica tu problema. Las probabilidades son buenas de que no será debido al lanzamiento. Nunca sacrifique código limpio y seguro en un intento de optimizarlo HASTA QUE SEPA QUE NECESITA.

Cuestiones relacionadas