2015-01-07 12 views
5

Przeczytałem w kilku komentarzach Briana Goetza, że ​​serializujące lambdy "mają znacznie wyższe koszty wydajności w porównaniu z niezaszyfrowanymi lambdami".Wydajność serializowanych lambd w Javie 8

Jestem ciekawy teraz: Gdzie dokładnie jest to nad głową i co go powoduje? Czy ma to wpływ tylko na instancję lambda, czy też na inwokację?

W poniższym kodzie, oba przypadki (callExistingInstance() i callWithNewInstance()) będą podlegały wpływowi serializacji "MyFunction" lub tylko drugiego przypadku?

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); 
    } 
} 

Odpowiedz

3

Uderzenie wydajności pojawia się w przypadku serializacji/deserializacji oraz podczas tworzenia instancji. Tylko twój drugi przykład przynosi trafienie. Powodem, dla którego jest to kosztowne, jest to, że gdy deserializujesz, podstawowa klasa twojej lambda jest tworzona przez rodzaj specjalnego odbicia (które ma zdolność tworzenia/definiowania klasy) zamiast zwykłego starego obiektu serializowanego (gdzie przyszedłaby definicja klasy z?), a także wykonać pewne kontrole bezpieczeństwa ...

+0

Dziękuję za miłe wyjaśnienie. –

+0

To wyjaśnienie nie stanowi problemu. Deserializujące obiekty będą zawsze używać kosztownego Odbicia niezależnie od tego, czy używasz wyrażeń lambda, czy nie. Ale kod pytania nie deserializuje niczego. – Holger

+0

Pytanie dotyczyło serializacji i używania serializowanych lambd, w przeciwieństwie do innych typów lamdbas. OP pytał, która część cyklu życia tych obiektów jest mniej wydajna niż normalne lambdy. Jego kod odróżnia się poprawnie między inwokacją a instancją, a jego pytanie bezpośrednio to prosi. Jak wskazano w odpowiedzi, dotyczy to dokładnie jednego z jego przypadków. Deserializowanie lambdas ma/bardzo różne rzeczy/niż deserializowanie innych obiektów ("specjalne odbicie") i jest z natury droższe. – BadZen

2

Zwykle część czasu wykonywania implementacji lambda generuje klasę, która zasadniczo składa się z pojedynczej metody implementacji. Informacje potrzebne do wygenerowania takiej klasy są podane przez wywołanie metody bootstrap do LambdaMetafactory.metafactory w czasie wykonywania.

Po włączeniu opcji Serializacja rzeczy stają się bardziej skomplikowane. Po pierwsze, skompilowany kod użyje alternatywnej metody bootstrapowej, która zapewnia większą elastyczność po cenie konieczności analizowania parametrów varargs zgodnie z flagami określonymi w tablicy parametrów.

Następnie generowane klasy lambda musi mieć writeReplace metody (patrz druga połowa Serializable documentation), która ma utworzyć i powrotu wystąpienie SerializedLambda zawierający wszystkie informacje wymagane do odtworzenia wystąpienie lambda. Ponieważ metoda pojedynczej implementacji klasy lambda składa się tylko z prostego wywołania delegowania, ta metoda writeReplace i związana z nią stała informacja będą zwielokrotniać rozmiar wygenerowanej klasy.

Warto również zauważyć, że klasa tworzenia tej Serializable instancji lambda będzie miał syntetyczna metoda $deserializeLambda$ (porównaj class documentation of SerializedLambda jako odpowiednik procesu lambda na writeReplace. To zwiększy wykorzystanie klas dysku i czas ładowania (ale nie wpływ na ocenę wyrażeń lambda).


W przykładzie kodu, obie metody mogą być naruszone przez tę samą ilość czasu jak ładowaniem i generowania klasy zdarza się tylko raz w wyrażeniu lambda. na kolejnych ocenach, 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) Mówimy tu o jednorazowym obciążeniu, nawet gdy baranek Wyrażenie da znajduje się w pętli, wpływa tylko na pierwszą iterację.

Pamiętaj, że jeśli masz wyrażenia lambda w pętli, nie może być nowy instancja stworzony dla każdej iteracji mając ją na zewnątrz pętli będzie na pewno mieć jedno wystąpienie podczas całej pętli. Ale to zachowanie nie zależy od tego, czy interfejs docelowy to Serializable. Zależy tylko od tego, czy wyrażenie zawiera wartość (porównaj z this answer).

Zauważ, że jeśli napisał

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

w drugiej metodzie (zanotować wyraźny final modyfikator) wartość toAdd byłaby stała kompilacji-czas i wyrażenie zostanie skompilowany jak gdybyś napisał (number) -> number + 1, tzn. nie będzie więcej przechwytywać wartości. Wtedy otrzymalibyśmy tę samą instancję lambda w każdej iteracji pętli (z aktualną wersją JVM Oracle). Zatem pytanie, czy nowa instancja jest tworzona, czasami zależy od małych bitów kontekstu. Ale zazwyczaj wpływ na wydajność jest raczej niewielki.

Powiązane problemy