들어가며
최근 코틀린을 계속 사용하며 아직까지 익숙하지 않은 키워드가 몇가지 있었는데요.
그 중 하나가 `Inline`, `noInline` 키워드였습니다.
이 글을 통해 해당 키워드의 쓰임새를 정리해보려고 합니다.
Kotlin 인라인 함수로 성능과 가독성 향상시키기
Kotlin에서 인라인 함수는 코드의 성능과 가독성을 향상시켜 줍니다.
함수 호출의 오버헤드를 제거함으로써, 인라인 함수는 더 빠른 실행과 더 나은 최적화를 합니다.
고차 함수와 인라인 키워드
고차 함수는 각 호출 시마다 새롭게 메모리를 할당합니다.
아래 코드를 디컴파일하여 바이트 코드를 보면, 각 호출 시 새로운 객체가 생성되고 있습니다.
이 함수가 계속 반복되는 for 루프에서 사용된다면 성능에 큰 문제가 발생할 수 있습니다.
fun main() {
noinlineFun { println("Hello, World!") }
}
// 고차함수
fun noinlineFun(function: () -> Unit) {
function()
}
아래 코드가 GPT에게 요청한 예상되는 디컴파일 결과입니다.
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
public final class MainKt {
public static final void main() {
noinlineFun((Function0)(new Function0() {
@NotNull
public final Unit invoke() {
System.out.println("Hello, World!");
return Unit.INSTANCE;
}
}));
}
public static final void noinlineFun(@NotNull Function0<Unit> function) {
Intrinsics.checkNotNullParameter(function, "function");
function.invoke();
}
}
그래서 `inline` 키워드를 통해 함수 호출이 아니라 컴파일 단계에 함수 자체를 같이 컴파일하여 최적화할 수 있는 것입니다.
fun main() {
inlineFun { println("Hello, World!") }
}
// 고차함수
inline fun inlineFun(function: () -> Unit) {
function()
}
fun main() {
// inlineFun 함수 호출부가 다음과 변경됩니다.
println("Hello, World!")
}
인라인 함수의 특징
- 인라인 함수는 로컬 반환뿐만 아니라 비로컬 반환도 허용합니다.
- 인라인 함수는 public 또는 internal로 선언되지 않은 한 주변 클래스나 Scope의 private 변수 및 메서드에 접근할 수 없습니다.
- 인라인 함수는 주로 2-3줄 정도의 짧은 함수에 사용합니다.
- 리플렉션을 사용하기 때문에 성능에 큰 영향을 미칠 수 있습니다.
- 인라인 함수는 재귀 호출할 수 없습니다.
crossinline
및 noinline
키워드
Crossinline
`crossinline` 키워드는 람다를 인라인 함수에 전달할 때 비로컬 반환을 방지하기 위해 사용됩니다.
fun main() {
performAction {
println("Start of action")
nestedAction {
println("Inside nested action")
// return // 비로컬 반환을 할 수 없습니다.
}
println("End of action")
}
}
inline fun performAction(action: () -> Unit) {
println("Performing action")
action()
}
inline fun nestedAction(crossinline nested: () -> Unit) {
println("Starting nested action")
nested()
println("Ending nested action")
}
Noinline
`inline` 키워드로 표시된 함수는 기본적으로 모든 함수 매개변수가 인라인되지만,
예를들어 반복문에서 사용하거나 프로젝트의 여러 부분에서 사용할 때 인라인이 되지 않기를 바라는 때도 있습니다.
fun main() {
performActions({
println("inlined 합니다.")
}, {
println("noinline 합니다.")
})
}
// 고차 함수
inline fun performActions(inlinedAction: () -> Unit, noinline noinlineAction: () -> Unit) {
println("Starting actions")
inlinedAction()
noinlineAction()
println("Ending actions")
}
이럴 때는 `noinline` 키워드로 컴파일시 함수를 복사하여 넣어주지 않고 함수를 호출합니다.
인라인 함수와 리턴
인라인 함수의 중요한 특징 중 하나는 비로컬 반환을 허용한다는 점입니다. 일반 함수에서는 람다 내에서 return
을 사용하면 로컬 반환만 가능합니다. 하지만 인라인 함수에서는 다음과 같이 비로컬 반환이 가능합니다:
inline fun inlineFunction(lambda: () -> Unit) {
lambda()
}
fun main() {
inlineFunction {
println("Before return")
return // 비로컬 반환
}
println("This line will not be executed")
}
하지만 비로컬 반환이 허용되지 않는 상황에서는 crossinline
키워드를 사용하여 이를 방지할 수 있습니다:
inline fun crossinlineFunction(crossinline lambda: () -> Unit) {
lambda()
}
fun main() {
crossinlineFunction {
println("Before return")
// return // 컴파일 오류 발생
}
println("This line will be executed")
}