коли використовувати вбудовану функцію в Котліні?


105

Я знаю, що вбудована функція, можливо, покращить продуктивність і призведе до зростання виробленого коду, але я не впевнений, коли правильно використовувати його.

lock(l) { foo() }

Замість створення об’єкта функції для параметра та генерації виклику компілятор міг видати наступний код. ( Джерело )

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

але я виявив, що не існує жодного об'єкта функції, створеного kotlin для функції, яка не є вбудованою. чому?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}

7
Для цього є два основні випадки використання, один - з певними типами функцій вищого порядку, а другий - параметри типового типу. Документація вбудованих функцій охоплює такі: kotlinlang.org/docs/reference/inline-functions.html
zsmb13

2
@ zsmb13 спасибі, сер. але я не розумію, що: "Замість створення об'єкта функції для параметра та генерації виклику компілятор міг випустити такий код"
holi-java

2
Я не отримую цього прикладу або tbh.
filthy_wizard

Відповіді:


279

Скажімо, ви створюєте функцію вищого порядку, яка приймає лямбда типу () -> Unit(немає параметрів, не повертає значення) і виконує її так:

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

На мові Java, це перекладається на щось подібне (спрощено!):

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

І коли ви телефонуєте з Котліна ...

nonInlined {
    println("do something here")
}

Під капотом Functionтут буде створений екземпляр , який загортає код всередині лямбда (знову це спрощено):

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

Отже, виклик цієї функції та передача їй лямбда завжди створюватиме екземпляр Functionоб'єкта.


З іншого боку, якщо ви використовуєте inlineключове слово:

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

Коли ви називаєте це так:

inlined {
    println("do something here")
}

Жоден Functionекземпляр не буде створений, натомість код навколо виклику blockвсередині вбудованої функції буде скопійовано на сайт виклику, тож ви отримаєте щось подібне у байт-коді:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

У цьому випадку нових екземплярів не створюється.


19
Яка перевага в першу чергу в обгортці об'єкта Function? тобто - чому не все вкладено?
Arturs Vancans

14
Таким чином, ви також можете довільно передавати функції навколо як параметри, зберігати їх у змінних тощо
zsmb13

6
Чудове пояснення @ zsmb13
Yajairo87

2
Ви можете, і якщо ви будете робити з ними складні речі, ви з часом хочете знати про ключові слова noinlineта crossinlineключові слова - перегляньте документи .
zsmb13

2
Документи дають причину, яку ви не хочете вставляти за замовчуванням: вбудовування може спричинити зростання згенерованого коду; однак, якщо ми зробимо це розумним способом (тобто уникаючи великих функцій), це окупиться в продуктивності, особливо на "мегаморфних" сайтах викликів всередині циклів.
CorayThan

43

Дозвольте додати: "Коли не використовувати inline" :

1) Якщо у вас є проста функція, яка не приймає інші функції як аргумент, не має сенсу їх вбудовувати. IntelliJ попередить вас:

Очікуваний вплив на вкладку "..." є незначним. Вбудовування найкраще працює для функцій з параметрами функціональних типів

2) Навіть якщо у вас є функція "з параметрами функціональних типів", ви можете зіткнутися з компілятором, який скаже вам, що вбудовування не працює. Розглянемо цей приклад:

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

Цей код не компілюється з помилкою:

Незаконне використання вбудованого параметра "операція" в "...". Додайте модифікатор 'noinline' до оголошення параметра.

Причина полягає в тому, що компілятор не в змозі вкласти цей код. Якщо operationвін не загорнутий в об'єкт (що мається на увазі, inlineоскільки ви хочете цього уникнути), як його взагалі можна присвоїти змінній? У цьому випадку компілятор пропонує зробити аргумент noinline. Наявність inlineфункції з однією noinlineфункцією не має сенсу, не робіть цього. Однак якщо є кілька параметрів функціональних типів, розгляньте, якщо потрібно, вкладіть деякі з них.

Ось ось кілька запропонованих правил:

  • Ви можете вбудовувати, коли всі параметри функціонального типу викликаються безпосередньо або передаються іншій вбудованій функції
  • Ви повинні мати рядок, коли ^ це так.
  • Ви не можете вбудовувати, коли параметр функції присвоюється змінній всередині функції
  • Вам слід розглянути можливість вбудовування, якщо хоча б один з параметрів вашого функціонального типу можна вкласти, скористайтеся noinlineіншими.
  • Ви не повинні вбудовувати величезні функції, подумайте про згенерований байт-код. Він буде скопійований у всі місця, з яких викликається функція.
  • Інший випадок використання - це reifiedпараметри типу, які вимагають використання inline. Прочитайте тут .

4
технічно ви все ще можете вбудовувати функції, які не приймають лямбда-виразів правильно? ing
rogue-one

3
@ logue-one Котлін не забороняє цього разу промальовувати. Автори мови просто стверджують, що користь від ефективності, ймовірно, буде незначною. Малі методи, ймовірно, будуть накреслені JVM під час оптимізації JIT, особливо якщо вони виконуються часто. Інший випадок, коли inlineможе бути шкідливим, це коли функціональний параметр викликається кілька разів у вбудованій функції, наприклад у різних умовних гілках. Я просто натрапив на випадок, коли через це дублювався весь байт-код для функціональних аргументів.
Майк Хілл

5

Найважливіший випадок, коли ми використовуємо вбудований модифікатор, це коли ми визначаємо утиліподібні функції з функціями параметрів. Колекція або обробка рядків (наприклад filter, mapабо joinToString) або просто окремі функції - прекрасний приклад.

Ось чому вбудований модифікатор є переважно важливою оптимізацією для розробників бібліотек. Вони повинні знати, як це працює і які його поліпшення та витрати. Ми повинні використовувати вбудований модифікатор у своїх проектах, коли ми визначаємо власні утилітні функції з параметрами типу функцій.

Якщо у нас немає параметра типу функції, рефікованого параметра типу і нам не потрібен не локальний зворот, то, швидше за все, ми не повинні використовувати вбудований модифікатор. Ось чому у нас буде попередження про Android Studio або IDEA IntelliJ.

Також є проблема розміру коду. Вбудована велика функція може різко збільшити розмір байт-коду, оскільки вона копіюється на кожен сайт виклику. У таких випадках ви можете повторно змінити функцію та витягнути код до звичайних функцій.


4

Функції вищого порядку дуже корисні, і вони дійсно можуть покращити reusabilityкод. Однак однією з найбільших проблем щодо їх використання є ефективність. Лямбда-вирази компілюються в класи (часто анонімні класи), а створення об'єктів на Java - це важка операція. Ми все ще можемо ефективно використовувати функції вищого порядку, зберігаючи всі переваги, роблячи вбудовані функції.

тут вводиться функція вбудованого зображення

Коли функція позначена як inline, під час компіляції коду компілятор замінить усі виклики функцій фактичним складом функції. Також лямбда-вирази, подані в якості аргументів, замінюються їх фактичним тілом. Вони будуть розглядатися не як функції, а як фактичний код.

Якщо коротко: - Inline -> замість того, щоб викликати їх, вони замінюються кодом тіла функції під час компіляції ...

У Котліні використання функції в якості параметра іншої функції (так званих функцій вищого порядку) відчуває себе природніше, ніж у Java.

Однак використання лямбдашів має деякі недоліки. Оскільки вони анонімні класи (і, отже, об’єкти), їм потрібна пам'ять (і навіть може додати до загальної кількості методів вашого додатка). Щоб цього уникнути, ми можемо вкласти свої методи.

fun notInlined(getString: () -> String?) = println(getString())

inline fun inlined(getString: () -> String?) = println(getString())

З наведеного вище прикладу : - Ці дві функції роблять точно те саме - друкуючи результат функції getString. Один накреслений, а один - ні.

Якщо ви перевірите декомпільований код Java, ви побачите, що методи абсолютно ідентичні. Це тому, що ключове слово inline - це інструкція компілятору скопіювати код на сайт виклику.

Однак якщо ми передаємо будь-який тип функції іншій функції, як нижче:

//Compile time error… Illegal usage of inline function type ftOne...
 inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
 }

Щоб вирішити це, ми можемо переписати свою функцію, як показано нижче:

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

Припустимо, у нас функція вищого порядку, як нижче:

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

Тут компілятор скаже нам не використовувати ключове слово inline, коли є лише один лямбда-параметр, і ми передаємо його іншій функції. Отже, ми можемо переписати вище функцію, як показано нижче:

fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
}

Примітка : - нам також довелося видалити ключове слово noinline, оскільки його можна використовувати лише для вбудованих функцій!

Припустимо, у нас є така функція ->

fun intercept() {
    // ...
    val start = SystemClock.elapsedRealtime()
    val result = doSomethingWeWantToMeasure()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    // ...}

Це прекрасно працює, але м'ясо логіки функції забруднене кодом вимірювання, що ускладнює вашим колегам працювати над тим, що відбувається. :)

Ось як вбудована функція може допомогти цьому коду:

      fun intercept() {
    // ...
    val result = measure { doSomethingWeWantToMeasure() }
    // ...
    }

     inline fun <T> measure(action: () -> T) {
    val start = SystemClock.elapsedRealtime()
    val result = action()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    return result
    }

Тепер я можу сконцентруватися на тому, щоб прочитати, що головний намір функції intercept (), не пропускаючи рядки коду вимірювання. Ми також виграємо від можливості повторного використання цього коду в інших місцях, де ми хочемо

inline дозволяє викликати функцію з аргументом лямбда в межах закриття ({...}), а не передавати міру, подібну лямбда (myLamda)


2

Один простий випадок, коли ви, можливо, захочете це, коли ви створюєте функцію util, яка займає призупинення блоку. Розглянемо це.

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

У цьому випадку наш таймер не прийме функцій призупинення. Щоб вирішити це, ви можете спокусити його також призупинити

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

Але тоді його можна використовувати лише із самих функцій співпрограми / призупинення. Тоді ви в кінцевому підсумку створите версію async та не-асинхронну версію цих утиліт. Проблема зникає, якщо зробити її вбудованою.

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

Ось ігровий майданчик котлін зі станом помилок. Зробіть таймер в черзі, щоб вирішити його.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.