Когда использовать встроенную функцию в Котлин?
Я знаю, что встроенная функция, возможно, улучшит производительность и заставит сгенерированный код расти, но я не уверен, когда это правильно использовать.
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();
}
}
Ответы
Ответ 1
Предположим, вы создаете функцию более высокого порядка, которая принимает лямбда типа () -> 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");
В этом случае новые экземпляры не создаются.
Ответ 2
Позвольте мне добавить: "Когда не использовать inline
":
1) Если у вас есть простая функция, которая не принимает другие функции в качестве аргумента, вставлять их не имеет смысла. IntelliJ предупредит вас:
Ожидаемое влияние на производительность при вставке "..." незначительно. Встраивание лучше всего подходит для функций с параметрами функциональных типов
2) Даже если у вас есть функция "с параметрами функциональных типов", вы можете встретить компилятор, который скажет вам, что вставка не работает. Рассмотрим этот пример:
inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
val o = operation //compiler does not like this
return o(param)
}
Этот код не скомпилируется с ошибкой:
Недопустимое использование встроенного параметра 'operation' в '...'. Добавьте модификатор noinline к объявлению параметра.
Причина в том, что компилятор не может встроить этот код. Если operation
не обернут в объект (что подразумевается в inline
, поскольку вы хотите этого избежать), как его вообще можно назначить переменной? В этом случае компилятор предлагает сделать аргумент noinline
. Наличие функции inline
с одной функцией noinline
не имеет никакого смысла, не делайте этого. Однако, если имеется несколько параметров функциональных типов, при необходимости рассмотрите возможность включения некоторых из них.
Итак, вот некоторые предлагаемые правила:
- вы можете встроить, когда все параметры функционального типа вызываются напрямую или передаются другой встроенной функции
- Вы должны встраивать, когда ^ имеет место.
- Вы не можете встроить, когда параметр функции назначается переменной внутри функции
- Вы должны рассмотреть возможность встраивания, если хотя бы один из параметров вашего функционального типа может быть встроен, используйте
noinline
для других.
- Вы не должны вставлять огромные функции, думать о сгенерированном байт-коде. Он будет скопирован во все места, откуда вызывается функция.
- Другой вариант использования - это параметры типа
reified
, которые требуют использования inline
. Читайте здесь.
Ответ 3
Наиболее важный случай, когда мы используем встроенный модификатор, - это когда мы определяем подобные утилитам функции с функциями параметров. joinToString
обработка коллекций или строк (например, filter
, ma
p или joinToString
) или просто автономные функции.
Вот почему встроенный модификатор является в основном важной оптимизацией для разработчиков библиотек. Они должны знать, как это работает, каковы его улучшения и затраты. Мы будем использовать встроенный модификатор в наших проектах, когда будем определять наши собственные функции util с параметрами типа функции.
Когда у нас нет параметра типа функции, параметра reified типа, и нам не требуется нелокальный возврат, тогда мы, скорее всего, не должны использовать встроенный модификатор. Вот почему у нас будет предупреждение о Android Studio или IDEA IntelliJ.
Также есть проблема с размером кода. Добавление большой функции может значительно увеличить размер байт-кода, поскольку он копируется на каждый сайт вызовов. В таких случаях вы можете реорганизовать функцию и извлечь код в обычные функции.
Ответ 4
Один простой случай, когда вам может понадобиться один, - это когда вы создаете функцию 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
}
Но тогда его можно использовать только из функций сопрограммы/приостановки. Затем вы получите асинхронную версию и не асинхронную версию этих утилит. Проблема исчезнет, если вы сделаете это встроенным.
inline fun timer(block: () -> Unit) {
// stuff
block()
// stuff
}
fun main() {
// timer can be used from anywhere now
timer { logic() }
launch {
timer { asyncLogic() }
}
}
Вот игровая площадка kotlin с ошибкой. Сделайте таймер встроенным, чтобы решить его.