2010-01-31 13 views
84

C'è un sovraccarico quando lanciamo oggetti di un tipo su un altro? O il compilatore risolve tutto e non ci sono costi in fase di esecuzione?Il casting Java introduce un sovraccarico? Perché?

È una cosa generale o ci sono casi diversi?

Ad esempio, supponiamo di disporre di un array di Object [], in cui ogni elemento potrebbe avere un tipo diverso. Ma sappiamo sempre che, per esempio, l'elemento 0 è un doppio, l'elemento 1 è una stringa. (So ​​che questo è un disegno sbagliato, ma supponiamo che dovessi farlo.)

Le informazioni sul tipo di Java sono ancora conservate in fase di esecuzione? O tutto viene dimenticato dopo la compilazione e se facciamo gli elementi (Double) [0], seguiremo semplicemente il puntatore e interpreteremo quegli 8 byte come un doppio, qualunque cosa sia?

Non sono molto chiaro su come i tipi sono fatti in Java. Se avete qualche raccomandazione su libri o articoli, allora grazie anche a voi.

+0

Le prestazioni di instanceof e casting sono abbastanza buone. Ho postato un po 'di tempo in Java7 su diversi approcci al problema qui: http://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it- throw-exception/28858680 # 28858680 – Wheezil

+0

Questa altra domanda ha risposte molto buone http://stackoverflow.com/questions/16741323/casting-performance-in-different-levels-when-casting-down – user454322

risposta

63

Ci sono 2 tipi di getto:

implicito casting, quando lanci da un tipo a un tipo più ampio, che è fatto automaticamente e non c'è in testa:

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

esplicita casting, quando si passa da un tipo più ampio a uno più stretto. Per questo caso, è necessario in modo esplicito utilizzare la fusione come quella:

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

In questo secondo caso, non v'è in testa in fase di esecuzione, perché i due tipi devono essere controllati e nel caso in cui casting non è fattibile, JVM devono gettare un ClassCastException.

Tratto da JavaWorld: The cost of casting

Casting viene utilizzato per la conversione tra tipi - tra i tipi di riferimento in particolare per il tipo di getto operazione in cui siamo interessati qui.

upcast operazioni (detti anche conversioni di ampliamento del Java Language Specification) convertono un riferimento sottoclasse ad un riferimento classe antenato . Questa operazione di fusione è in genere automatica, poiché è sempre sicura e può essere implementata direttamente dal compilatore .

Downcast operazioni (chiamate anche conversioni restringimento del Java Language Specification) convertono un riferimento di classe antenato di una sottoclasse di riferimento. Questa operazione di colata crea overhead di esecuzione, dal momento che Java richiede che il cast sia controllato al runtime per assicurarsi che sia valido. Se l'oggetto si fa riferimento non è un istanza sia del tipo di destinazione per il cast o una sottoclasse di quel tipo, il tentativo di cast non è consentito e deve gettare un java.lang.ClassCastException .

+79

Questo articolo JavaWorld ha più di 10 anni vecchio, quindi prendo tutte le dichiarazioni che fa sulle prestazioni con una grana molto grande del tuo sale migliore. – skaffman

+0

@skaffman, infatti, prenderei qualsiasi affermazione che faccia (indipendentemente dal fatto che riguardi le prestazioni di no) con un pizzico di sale. – Pacerier

+0

sarà lo stesso caso, se non assegno l'oggetto casted al riferimento e chiamo semplicemente il metodo su di esso? come '((String) o) .someMethodOfCastedClass()' –

7

Ad esempio, supponiamo di avere un array di Object [], dove ogni elemento può avere un tipo differente. Ma sappiamo sempre che, per esempio, l'elemento 0 è un doppio, l'elemento 1 è una stringa. (So ​​che questo è un disegno sbagliato, ma supponiamo che dovessi farlo.)

Il compilatore non rileva i tipi dei singoli elementi di un array. Controlla semplicemente che il tipo di ogni espressione di elemento sia assegnabile al tipo di elemento dell'array.

Le informazioni sul tipo di Java sono ancora conservate in fase di esecuzione? O tutto viene dimenticato dopo la compilazione e se facciamo gli elementi (Double) [0], seguiremo semplicemente il puntatore e interpreteremo quegli 8 byte come un doppio, qualunque cosa sia?

Alcune informazioni vengono mantenute in fase di esecuzione, ma non i tipi statici dei singoli elementi. Puoi dire questo guardando il formato del file di classe.

È teoricamente possibile che il compilatore JIT possa utilizzare "analisi di escape" per eliminare verifiche di tipo non necessarie in alcuni compiti. Tuttavia, fare questo nel modo che stai suggerendo sarebbe oltre i limiti dell'ottimizzazione realistica. Il payoff dell'analisi dei tipi di singoli elementi sarebbe troppo piccolo.

Inoltre, le persone non dovrebbero scrivere codice di applicazione del genere comunque.

+0

E i primitivi? '(float) Math.toDegrees (theta)' Ci sarà un overhead significativo anche qui? –

+1

C'è un sovraccarico per alcuni cast primitivi. Se sia significativo dipende dal contesto. –

5

L'istruzione di codice byte per eseguire il casting in fase di esecuzione è denominata checkcast. È possibile disassemblare il codice Java utilizzando javap per vedere quali istruzioni sono state generate.

Per gli array, Java mantiene le informazioni sul tipo in fase di esecuzione. La maggior parte delle volte, il compilatore rileva errori di tipo per te, ma ci sono casi in cui ti imbatterai in un ArrayStoreException quando proverai a memorizzare un oggetto in un array, ma il tipo non corrisponde (e il compilatore non l'ha catturato). Il Java language spec fornisce il seguente esempio:

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 è valido dal ColoredPoint è una sottoclasse di punto, ma pa[0] = new Point() non è valido.

Questo è contrario ai tipi generici, in cui non sono conservate informazioni di tipo in fase di esecuzione. Il compilatore inserisce le istruzioni checkcast laddove necessario.

Questa differenza nella digitazione per tipi e matrici generici rende spesso inadatto il mix di matrici e tipi generici.

37

Per una ragionevole implementazione di Java:

Ogni oggetto ha un'intestazione contenente, tra l'altro, un puntatore al tipo runtime (ad esempio Double o String, ma potrebbe non essere CharSequence o AbstractList).Supponendo che il compilatore di runtime (generalmente HotSpot nel caso di Sun) non sia in grado di determinare staticamente il tipo, è necessario eseguire alcune verifiche dal codice macchina generato.

Prima di leggere il puntatore del tipo di runtime. Questo è necessario per chiamare comunque un metodo virtuale in una situazione simile.

Per il casting di un tipo di classe, è noto esattamente quante superclassi ci sono fino a quando non si preme java.lang.Object, quindi il tipo può essere letto con un offset costante dal puntatore del tipo (in realtà i primi otto in HotSpot). Anche in questo caso è analogo alla lettura di un puntatore del metodo per un metodo virtuale.

Quindi il valore di lettura richiede solo un confronto con il tipo statico previsto del cast. A seconda dell'architettura del set di istruzioni, un'altra istruzione dovrà essere derivata (o guasto) su un ramo errato. ISA come ARM a 32 bit hanno istruzioni condizionali e potrebbero essere in grado di far passare il percorso triste attraverso il percorso felice.

Le interfacce sono più difficili a causa dell'ereditarietà dell'interfaccia. Generalmente gli ultimi due cast di interfacce sono memorizzati nella cache nel tipo di runtime. Nei primissimi giorni (oltre un decennio fa), le interfacce erano un po 'lente, ma non è più rilevante.

Speriamo che si possa vedere che questo genere di cose è in gran parte irrilevante per le prestazioni. Il tuo codice sorgente è più importante. In termini di prestazioni, il più grande successo nel tuo scenario è suscettibile di essere carenza di cache dall'inseguire puntatori di oggetti dappertutto (le informazioni sul tipo saranno ovviamente comuni).

+1

interessante - quindi questo significa che per le classi non-interface se scrivo sottoclassi Superclass sc = (Superclasse); che il compilatore (jit ie: load time) "staticamente" inserirà l'offset da Object in ciascuna delle classi Superclass e SubClass nelle loro intestazioni "Class" e quindi tramite un semplice add + compare essere in grado di risolvere le cose? - Questo è bello e veloce :) Per le interfacce, non assumerei peggio di un piccolo hashtable o btree? – peterk

+0

@peterk Per la trasmissione tra classi, l'indirizzo dell'oggetto e il "vtbl" (tabella dei puntatori del metodo, oltre alla tabella della gerarchia delle classi, della cache dell'interfaccia, ecc.) Non cambiano. Quindi il cast [class] controlla il tipo, e se è adatto a nient'altro deve accadere. –

1

In teoria, è stato introdotto un sovraccarico. Tuttavia, le JVM moderne sono intelligenti. Ogni implementazione è diversa, ma non è irragionevole supporre che possa esistere un'implementazione che JIT ha ottimizzato i controlli di casting quando poteva garantire che non ci sarebbe mai stato un conflitto. Per quanto riguarda le JVM specifiche che offrono questo, non potrei dirtelo. Devo ammettere che mi piacerebbe conoscere da solo le specifiche dell'ottimizzazione del JIT, ma queste sono le preoccupazioni degli ingegneri della JVM.

La morale della storia è scrivere codice comprensibile prima. Se riscontri rallentamenti, profilo e identifica il tuo problema. Le probabilità sono buone che non sarà dovuto al casting. Non sacrificare mai il codice pulito e sicuro nel tentativo di ottimizzarlo FINO A CHE NON AVETE BISOGNO.