Musisz dokonać rozróżnienia między częstymi wykonaniami tej samej strony wywołań , dla bezstanowych lambda lub stanowych lambd, a częstym użyciem odwołania do metody do tej samej metody (przez różne strony wywołań).
Spójrz na następujące przykłady:
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=System::gc;
if(r1==null) r1=r2;
else System.out.println(r1==r2? "shared": "unshared");
}
Tutaj ta sama strona wywołania jest wykonywana dwa razy, tworząc bezstanową lambdę, a bieżąca implementacja zostanie wydrukowana "shared"
.
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=Runtime.getRuntime()::gc;
if(r1==null) r1=r2;
else {
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
}
}
W tym drugim przykładzie ta sama witryna wywołania jest wykonywana dwa razy, tworząc lambdę zawierającą odwołanie do Runtime
instancji, a bieżąca implementacja zostanie wydrukowana "unshared"
ale "shared class"
.
Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
W przeciwieństwie do tego w ostatnim przykładzie są dwie różne witryny wywołań generujące równoważne odwołanie do metody, ale od tego 1.8.0_05
momentu zostaną wydrukowane "unshared"
i "unshared class"
.
Dla każdego wyrażenia lambda lub odwołania do metody kompilator wyemituje invokedynamic
instrukcję, która odwołuje się do metody ładowania początkowego dostarczonej przez środowisko JRE w klasie LambdaMetafactory
oraz statycznych argumentów niezbędnych do utworzenia żądanej klasy implementacji lambda. To, co produkuje fabryka meta, pozostawia faktycznemu środowisku JRE, ale jest to określone zachowanie invokedynamic
instrukcji, aby zapamiętać i ponownie użyć CallSite
instancji utworzonej przy pierwszym wywołaniu.
Bieżące środowisko JRE tworzy obiekt stały ConstantCallSite
zawierający a MethodHandle
dla bezstanowych lambd (i nie ma żadnego powodu, aby robić to inaczej). A odwołania do static
metod są zawsze bezstanowe. Tak więc w przypadku lambd bezstanowych i witryn z pojedynczym wywołaniem odpowiedź musi brzmieć: nie buforuj, JVM zrobi, a jeśli nie, musi mieć mocne powody, których nie powinieneś przeciwdziałać.
W przypadku lambd mających parametry i this::func
jest to lambda, które ma odniesienie do this
instancji, sytuacja wygląda nieco inaczej. Środowisko JRE może je buforować, ale oznaczałoby to utrzymywanie pewnego rodzaju Map
między rzeczywistymi wartościami parametrów a wynikową lambdą, co mogłoby być bardziej kosztowne niż tylko ponowne utworzenie tej prostej strukturalnej instancji lambda. Bieżące środowisko JRE nie buforuje wystąpień lambda mających stan.
Ale to nie znaczy, że klasa lambda jest tworzona za każdym razem. Oznacza to po prostu, że rozwiązana witryna wywołania będzie zachowywać się jak zwykła konstrukcja obiektu tworząca instancję klasy lambda, która została wygenerowana przy pierwszym wywołaniu.
Podobne rzeczy dotyczą odwołań do metod do tej samej metody docelowej utworzonej przez różne witryny wywołań. JRE może współdzielić między sobą jedną instancję lambda, ale w obecnej wersji nie, najprawdopodobniej dlatego, że nie jest jasne, czy utrzymanie pamięci podręcznej się opłaci. Tutaj nawet wygenerowane klasy mogą się różnić.
Zatem buforowanie, jak w twoim przykładzie, może sprawić, że twój program będzie robił inne rzeczy niż bez. Ale niekoniecznie bardziej wydajne. Obiekt buforowany nie zawsze jest bardziej wydajny niż obiekt tymczasowy. O ile naprawdę nie mierzysz wpływu na wydajność spowodowanego przez tworzenie lambda, nie powinieneś dodawać żadnego buforowania.
Myślę, że istnieją tylko specjalne przypadki, w których buforowanie może być przydatne:
- mówimy o wielu różnych witrynach z połączeniami odnoszących się do tej samej metody
- lambda jest tworzona w konstruktorze / klasie inicjalizacji, ponieważ później strona użycia będzie
- być wywoływane przez wiele wątków jednocześnie
- cierpią z powodu słabszej wydajności pierwszej inwokacji