2015-01-07 26 views
5

Ho letto in alcuni commenti di Brian Goetz che i lambda serializzabili "hanno costi di prestazione significativamente più alti rispetto ai lambda non graduabili".Prestazioni di lambda serializzabili in Java 8

Sono curioso ora: dov'è esattamente quello overhead e cosa lo causa? Influisce solo sull'istanza di un lambda o anche sull'invocazione?

Nel codice seguente, entrambi i casi (callExistingInstance() e callWithNewInstance()) sono interessati dalla serializzabilità di "MyFunction" o solo dal secondo caso?

interface MyFunction<IN, OUT> { 
    OUT call(IN arg); 
} 

void callExistingInstance() { 

    long toAdd = 1; 
    long value = 0; 

    final MyFunction<Long, Long> adder = (number) -> number + toAdd; 

    for (int i = 0; i < LARGE_NUMBER; i++) { 
     value = adder.call(value); 
    } 
} 

void callWithNewInstance() { 

    long value = 0; 

    for (int i = 0; i < LARGE_NUMBER; i++) { 
     long toAdd = 1; 

     MyFunction<Long, Long> adder = (number) -> number + toAdd; 

     value = adder.call(value); 
    } 
} 

risposta

3

il calo di prestazioni viene quando si serializzare/deserializzare, e quando si crea un'istanza. Solo il tuo secondo esempio prende il colpo. Il motivo per cui è costoso è che quando si deserializza, la classe sottostante del tuo lambda viene istanziata da una sorta di riflessione speciale (che ha la capacità di creare/definire una classe) piuttosto che un semplice oggetto serializzato (dove verrebbe la definizione della classe da?), oltre a eseguire alcuni controlli di sicurezza ...

+0

Grazie per la bella spiegazione. –

+0

Questa spiegazione non sta colpendo il problema. La deserializzazione degli oggetti utilizzerà sempre il costoso Reflection indipendentemente dal fatto che si utilizzino o meno espressioni lambda. Ma il codice della domanda non deserializza nulla. – Holger

+0

La domanda riguardava la serializzazione e l'uso di lambda serializzabili, al contrario di altri tipi di lamdbas. OP chiedeva quale parte del ciclo di vita di quegli oggetti fosse meno performante del normale lambda. Il suo codice distingue correttamente tra invocazione e istanziazione, e la sua domanda lo chiede direttamente. Come la risposta ha indicato, uno dei suoi casi è interessato. Deserializzare lambda fa/cose molto diverse/che deserializzare altri oggetti ("riflessione speciale") ed è intrinsecamente più costoso. – BadZen

2

Normalmente, la parte runtime dell'implementazione lambda genererà una classe che consiste essenzialmente in un singolo metodo di implementazione. Le informazioni necessarie per generare tale classe sono date da un richiamo del metodo bootstrap a LambdaMetafactory.metafactory in fase di runtime.

Quando si abilita la serializzazione, le cose si complicano. Innanzitutto, il codice compilato utilizzerà il metodo di bootstrap alternativo LambdaMetafactory.altMetafactory che offre una maggiore flessibilità al prezzo di dover analizzare i parametri varargs in base ai flag specificati all'interno dell'array dei parametri.

Poi la classe lambda generata deve avere un metodo writeReplace (vedere la seconda metà del Serializable documentation) che deve creare e restituire un'istanza SerializedLambda contenente tutte le informazioni necessarie per ricreare l'istanza lambda. Poiché il metodo di implementazione singola di una classe lambda consiste in una semplice chiamata di delega, il metodo writeReplace e le relative informazioni costanti moltiplicheranno la dimensione della classe generata.

È anche interessante notare che la classe crea l'istanza lambda Serializable avrà un metodo sintetico $deserializeLambda$ (confrontare la class documentation of SerializedLambda come controparte al processo di del lambda writeReplace. Che aumenterà l'utilizzo classi disco e tempo di caricamento (ma non influisce sulla valutazione delle espressioni lambda).


Nel codice esempio, entrambi i metodi sarebbero influenzati dalla stessa quantità di tempo come bootstrapping e generazione classe avviene solo una volta per un'espressione lambda. In seguito a valutazioni, the class generated on the first evaluation will be re-used and only a new instance created (if not even the instance is re-used) Stiamo parlando di una volta in testa qui, anche quando l'agnello l'espressione da è contenuta in un ciclo, influisce solo sulla prima iterazione.

Si noti che se si dispone di un'espressione lambda all'interno di un ciclo, non ci potrebbe essere una nuova istanza creato per ogni iterazione, pur avendo al di fuori del ciclo sarà di sicuro avere un'istanza durante l'intero ciclo. Ma questo comportamento non dipende dalla domanda se l'interfaccia di destinazione è Serializable. Dipende semplicemente dal fatto che l'espressione acquisisca valore (confrontare con this answer).

Nota che se avessi scritto

final long toAdd = 1; 
MyFunction<Long, Long> adder = (number) -> number + toAdd; 

nel secondo metodo (si noti l'esplicito final modificatore) il valore toAdd sarebbe una fase di compilazione costante e l'espressione viene compilata come se tu avessi scritto (number) -> number + 1, cioè non catturerà più un valore. Quindi si otterrebbe la stessa istanza lambda in ogni ciclo iterativo (con la versione corrente di Oracle JVM). Quindi la domanda se una nuova istanza viene creata a volte dipende da piccoli frammenti del contesto. Ma di solito, l'impatto sulle prestazioni è piuttosto limitato.