Wiem, że funkcja wbudowana może poprawić wydajność i spowodować wzrost generowanego kodu, ale nie jestem pewien, kiedy należy jej użyć.
lock(l) { foo() }
Zamiast tworzyć obiekt funkcji dla parametru i generować wywołanie, kompilator może wyemitować następujący kod. ( Źródło )
l.lock()
try {
foo()
}
finally {
l.unlock()
}
ale odkryłem, że nie ma obiektu funkcji utworzonego przez kotlin dla funkcji nieliniowej. czemu?
/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
lock.lock();
try {
block();
} finally {
lock.unlock();
}
}
function
kotlin
inline-functions
holi-java
źródło
źródło
inline
funkcja może poprawić wydajność i że byłaby zauważalna przez użytkownika?Odpowiedzi:
Powiedzmy, że tworzysz funkcję wyższego rzędu, która przyjmuje lambdę typu
() -> Unit
(bez parametrów, bez wartości zwracanej) i wykonuje ją w następujący sposób:W języku Java przełoży się to na coś takiego (uproszczone!):
A kiedy dzwonisz z Kotlina ...
Pod maską
Function
zostanie utworzona tutaj instancja will, która otacza kod wewnątrz lambdy (znowu jest to uproszczone):Więc w zasadzie wywołanie tej funkcji i przekazanie do niej lambdy zawsze tworzy instancję
Function
obiektu.Z drugiej strony, jeśli używasz
inline
słowa kluczowego:Kiedy nazywasz to w ten sposób:
Żadna
Function
instancja nie zostanie utworzona, zamiast tego kod wokół wywołaniablock
funkcji wbudowanej zostanie skopiowany do witryny wywołania, więc w kodzie bajtowym otrzymasz coś takiego:W takim przypadku nie są tworzone żadne nowe instancje.
źródło
noinline
icrossinline
- zobacz dokumentację .Dodam: kiedy nie używać
inline
:Jeśli masz prostą funkcję, która nie akceptuje innych funkcji jako argumentu, nie ma sensu ich wstawiać. IntelliJ ostrzeże Cię:
Nawet jeśli masz funkcję „z parametrami typów funkcjonalnych”, możesz napotkać kompilator, który poinformuje Cię, że wstawianie nie działa. Rozważmy ten przykład:
Ten kod nie skompiluje się, powodując błąd:
Powodem jest to, że kompilator nie może wstawić tego kodu, zwłaszcza
operation
parametru. Jeślioperation
nie jest opakowany w obiekt (co byłoby wynikiem zastosowaniainline
), jak w ogóle można go przypisać do zmiennej? W tym przypadku kompilator sugeruje przedstawienie argumentunoinline
. Posiadanieinline
funkcji z pojedyncząnoinline
funkcją nie ma sensu, nie rób tego. Jeśli jednak istnieje wiele parametrów typów funkcjonalnych, rozważ wstawienie niektórych z nich, jeśli jest to wymagane.Oto kilka sugerowanych zasad:
noinline
dla innych.reified
parametry typu, które wymagają użyciainline
. Przeczytaj tutaj .źródło
inline
może być szkodliwy, jest wielokrotne wywoływanie parametru funkcjonalnego w funkcji wbudowanej, na przykład w różnych gałęziach warunkowych. Właśnie natknąłem się na przypadek, w którym cały kod bajtowy dla argumentów funkcjonalnych był z tego powodu kopiowany.crossinline
parametrów?Najważniejszym przypadkiem, gdy używamy modyfikatora inline, jest definiowanie funkcji użytkowych z funkcjami parametrycznymi. Gromadzenie lub przetwarzanie ciąg (jak
filter
,map
lubjoinToString
) lub funkcje tylko jednostkowe są doskonałym przykładem.Dlatego modyfikator wbudowany jest głównie ważną optymalizacją dla programistów bibliotek. Powinni wiedzieć, jak to działa i jakie są jego ulepszenia i koszty. Powinniśmy używać inline modyfikatora w naszych projektach, kiedy definiujemy własne funkcje util z parametrami typu funkcji.
Jeśli nie mamy parametru typu funkcji, parametru typu reified i nie potrzebujemy nielokalnego zwrotu, najprawdopodobniej nie powinniśmy używać modyfikatora inline. Dlatego będziemy mieć ostrzeżenie na temat Android Studio lub IDEA IntelliJ.
Ponadto występuje problem z rozmiarem kodu. Wbudowanie dużej funkcji może znacznie zwiększyć rozmiar kodu bajtowego, ponieważ jest on kopiowany do każdej strony wywołania. W takich przypadkach można refaktoryzować funkcję i wyodrębniać kod do zwykłych funkcji.
źródło
Funkcje wyższego rzędu są bardzo pomocne i mogą naprawdę poprawić
reusability
kod. Jednak jednym z największych problemów związanych z ich używaniem jest wydajność. Wyrażenia lambda są kompilowane do klas (często klas anonimowych), a tworzenie obiektów w Javie to ciężka operacja. Nadal możemy efektywnie korzystać z funkcji wyższego rzędu, zachowując wszystkie korzyści, dzięki wbudowaniu funkcji.tu pojawia się funkcja inline w obraz
Gdy funkcja jest oznaczona jako
inline
, podczas kompilacji kodu kompilator zastąpi wszystkie wywołania funkcji rzeczywistą treścią funkcji. Ponadto wyrażenia lambda podane jako argumenty są zastępowane ich rzeczywistą treścią. Nie będą traktowane jako funkcje, ale jako rzeczywisty kod.W skrócie: - Inline -> zamiast być wywoływane, są zastępowane przez kod funkcji w czasie kompilacji ...
W Kotlinie użycie funkcji jako parametru innej funkcji (tzw. Funkcji wyższego rzędu) wydaje się bardziej naturalne niż w Javie.
Używanie lambd ma jednak pewne wady. Ponieważ są to klasy anonimowe (a zatem obiekty), potrzebują pamięci (a nawet mogą zwiększyć ogólną liczbę metod Twojej aplikacji). Aby tego uniknąć, możemy wprowadzić nasze metody.
Z powyższego przykładu : - Te dwie funkcje robią dokładnie to samo - wypisują wynik funkcji getString. Jedna jest wstawiona, a druga nie.
Jeśli sprawdzisz zdekompilowany kod java, zobaczysz, że metody są całkowicie identyczne. Dzieje się tak, ponieważ słowo kluczowe inline jest instrukcją dla kompilatora, aby skopiować kod do witryny wywołania.
Jeśli jednak przekazujemy dowolny typ funkcji do innej funkcji, jak poniżej:
Aby rozwiązać ten problem, możemy przepisać naszą funkcję jak poniżej:
Załóżmy, że mamy funkcję wyższego rzędu, jak poniżej:
W tym przypadku kompilator powie nam, abyśmy nie używali słowa kluczowego inline, gdy jest tylko jeden parametr lambda i przekazujemy go do innej funkcji. Możemy więc przepisać powyższą funkcję jak poniżej:
Uwaga : -Musieliśmy również usunąć słowo kluczowe noinline, ponieważ może być używane tylko do funkcji wbudowanych!
Załóżmy, że mamy taką funkcję ->
Działa to dobrze, ale logika funkcji jest zanieczyszczona kodem pomiarowym, co utrudnia kolegom pracę nad tym, co się dzieje. :)
Oto jak funkcja wbudowana może pomóc w tym kodzie:
Teraz mogę skoncentrować się na czytaniu, co jest głównym celem funkcji intercept () bez pomijania linii kodu pomiarowego. Korzystamy również z możliwości ponownego wykorzystania tego kodu w innych miejscach, w których chcemy
inline pozwala na wywołanie funkcji z argumentem lambda w zamknięciu ({...}) zamiast przekazywania miary podobnej do lambda (myLamda)
Kiedy jest to przydatne?
Słowo kluczowe inline jest przydatne w przypadku funkcji, które akceptują inne funkcje lub wyrażenia lambda jako argumenty.
Bez słowa kluczowego inline w funkcji argument lambda tej funkcji jest konwertowany w czasie kompilacji na wystąpienie interfejsu funkcji za pomocą jednej metody o nazwie invoke (), a kod w wyrażeniu lambda jest wykonywany przez wywołanie metody invoke () w tej instancji funkcji wewnątrz treści funkcji.
W przypadku słowa kluczowego wbudowanego w funkcji ta konwersja podczas kompilacji nigdy nie występuje. Zamiast tego treść funkcji wbudowanej zostaje wstawiona w miejscu wywołania, a jej kod jest wykonywany bez narzutu związanego z tworzeniem instancji funkcji.
Hmmm? Przykład w Androidzie ->
Powiedzmy, że mamy funkcję w klasie routera aktywności, która uruchamia działanie i stosuje pewne dodatki
Ta funkcja tworzy intencję, stosuje pewne dodatki, wywołując argument funkcji applyExtras i rozpoczyna działanie.
Jeśli spojrzymy na skompilowany kod bajtowy i zdekompilujemy go na Javę, wygląda to mniej więcej tak:
Powiedzmy, że nazywamy to z poziomu odbiornika kliknięć w działaniu:
Zdekompilowany kod bajtowy tego odbiornika kliknięć wyglądałby wtedy następująco:
Nowa instancja funkcji Function1 jest tworzona za każdym razem, gdy jest uruchamiany odbiornik kliknięć. To działa dobrze, ale nie jest idealne!
Teraz po prostu dodajmy inline do naszej metody routera aktywności:
Bez zmiany naszego kodu odbiornika kliknięć możemy teraz uniknąć tworzenia tej instancji Function1. Odpowiednik Java kodu nasłuchiwania kliknięć wyglądałby teraz mniej więcej tak:
Otóż to.. :)
Umieszczanie funkcji w tekście oznacza po prostu skopiowanie jej treści i wklejenie w miejscu wywołania funkcji. Dzieje się to w czasie kompilacji.
źródło
inline functions
. Czy możesz podać przykład użycia ze świata rzeczywistego (tj. W prawdziwym projekcie, nad którym pracowałeś)?Jednym prostym przypadkiem, w którym możesz chcieć, jest utworzenie funkcji util, która przyjmuje blok zawieszenia. Rozważ to.
W takim przypadku nasz zegar nie zaakceptuje funkcji wstrzymania. Aby go rozwiązać, możesz ulec pokusie, aby go również zawiesić
Ale wtedy można go używać tylko z samych programów / funkcji wstrzymania. Następnie utworzysz wersję asynchroniczną i nie-asynchroniczną tych narzędzi. Problem znika, jeśli umieścisz go w linii.
Oto plac zabaw kotlin ze stanem błędu. Ustaw licznik czasu w linii, aby go rozwiązać.
źródło
inline fun
. Czy możesz wyjaśnić?