안녕하세요.
이번 포스팅에서는 Inline과 reified 키워드에 대해 알아보려고 합니다.
inline 키워드는 함수에 붙는 키워드로 많이 볼수가 있는데요.
가령 흔히 쓰이는 Collection 함수에서 forEach 문을 보면 아래처럼 inline을 사용하는 것을 볼 수 있습니다.
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
inline 이란 뜻을 그대로 해석해보면 line 안에 라는 뜻으로 해석할 수 있는데
이말은 즉 코드를 라인안에 넣는다는 의미로 해석하시면 될 거같습니다.
우리가 고차함수(함수를 parameter로 넣거나 결과를 반환하는 함수)를 사용할때 람다식을 많이 사용하는데 이 람다식에 대한
함수를 새로 생성하느냐 아니면 람다식의 코드를 그대로 넣느냐를 결정하는게 inline 키워드의 핵심이라고 보시면 되겠습니다.
구체적으로 살펴보면
inline을 붙였을 때와 안붙였을때 decompile 하여 코드를 봤을 때
inline이 붙은 함수는 코드가 그대로 들어가고 inline이 붙지 않은 함수는 함수 인스턴스를 생성하서 수행한다는 것을 알 수 있습니다.
즉 noInline 함수는 함수 인스턴스에 대한 메모리할당, virtual call로 인한 런타임 오버헤드를 높이는 것으로 이해하시면 됩니다.
public static final void main() {
List list = CollectionsKt.emptyList();
Iterable $this$inlineForEach$iv = (Iterable)list;
int $i$f$inlineForEach = false;
Iterator var3 = $this$inlineForEach$iv.iterator();
while(var3.hasNext()) {
Object element$iv = var3.next();
String it = (String)element$iv;
int var6 = false;
System.out.println(it);
}
noInlineForEach((Iterable)list, (Function1)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
public static final void inlineForEach(@NotNull Iterable $this$inlineForEach, @NotNull Function1 action) {
int $i$f$inlineForEach = 0;
Intrinsics.checkNotNullParameter($this$inlineForEach, "$this$inlineForEach");
Intrinsics.checkNotNullParameter(action, "action");
Iterator var4 = $this$inlineForEach.iterator();
while(var4.hasNext()) {
Object element = var4.next();
action.invoke(element);
}
}
public static final void noInlineForEach(@NotNull Iterable $this$noInlineForEach, @NotNull Function1 action) {
Intrinsics.checkNotNullParameter($this$noInlineForEach, "$this$noInlineForEach");
Intrinsics.checkNotNullParameter(action, "action");
Iterator var3 = $this$noInlineForEach.iterator();
while(var3.hasNext()) {
Object element = var3.next();
action.invoke(element);
}
}
함수를 inline하면 여러가지 장점이 몇개 있는데
위에서 설명드린 런타임 오버헤드가 줄어드는것과 람다식안에 return 과 같은 함수 종료문도 쓸수 있게 됩니다.
왜냐하면 디컴파일 될때 같은 local control flow(같은 함수 범위) 을 타고 있기 때문입니다.
inline기능을 사용하면서 local control flow 을 막으려면 crossInline 키워드를 사용하면 됩니다.
또한 만약 람다로 들어가는 paramter가 여러개일때 특정 람다식에 대해서 noInline을 사용하면 디컴파일 때 inline 적용이 안되게 할 수 있습니다. 가령 람다식을 변수로 취해서 사용하는 경우 noInline을 붙이면 되겠습니다.
위에서 보면 Inline이 완벽해 보이나 Inline에도 단점이 존재합니다.
1. private일때 사용이 불가능
2. 바이트 코드 길이가 증가하는 것
결과적으로 inline은 open 함수이면서 길이가 짧은 코드에 적용하는게 효과적이라 볼 수 있습니다.
다음은 Inline에 대한 예제 코드입니다.
fun main() {
val list = listOf<String>()
list.inlineForEach(
inlineAction = {
if (it.isEmpty()) return
println(it)
},
crossInlineAction = {
//Error if(it.isEmpty()) return
println(it)
},
noInlineAction = {
//Error if (it.isEmpty()) return
println(it)
}
)
list.noInlineForEach {
println(it)
}
}
inline fun <T> Iterable<T>.inlineForEach(
inlineAction: (T) -> Unit,
crossinline crossInlineAction: (T) -> Unit,
noinline noInlineAction: (T) -> Unit
): Unit {
for (element in this) {
inlineAction(element)
noInlineAction(element)
crossInlineAction(element)
}
}
fun <T> Iterable<T>.noInlineForEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
다음은 reified에 대해 알아보겠습니다.
refied는 inline 함수에서 사용할 수 있는 키워드입니다.
generic type은 일반적인 경우에 reified type parameter 로 사용할 수 없습니다.
즉 구체화된 타입이 지정되어 있지 않기 때문에 reified type 을 따로 넘겨주거나 하는 불편한 코드가 들어가게 됩니다.
이때 inline함수에 reified를 사용하면 이 문제를 해결 할 수 있습니다.
fun <T> noInlineGenericTest(data: T) {
when (T::class) { //T는 reified type parameter 사용할 수 없다.
Int::class -> {
println("noInlineGenericTest Int: $data")
}
String::class -> {
println("noInlineGenericTest String: $data")
}
}
}
inline fun <reified T> inlineGenericTest(data: T) {
when (T::class) { //코드가 그대로 들어가기 때문에
Int::class -> {
println("inlineGenericTest Int: $data")
}
String::class ->{
println("inlineGenericTest String: $data")
}
}
}
아래는 50줄의 함수 코드를 inline으로 변경했을때 50개일때와 500줄일때 바이트코드 비교 화면입니다.
즉 50줄짜리 inline함수를 10번 호출하면 4배 정도 크기가 증가한 것으로 볼 수 있습니다. 다만
row의 크기에 따라 파일 크기가 달라질 수 있습니다.
1번 호출했을때
10번 호출했을때
'기타 > 코틀린' 카테고리의 다른 글
[Kotlin] Sequence vs Collections (0) | 2022.07.05 |
---|---|
[Kotlin] map, flatMap 비교 (0) | 2022.06.21 |
코틀린 특징 및 자바와 차이점 정리(작성중) (0) | 2021.10.04 |
코틀린 Sealed Class와 Enum Class (0) | 2021.07.01 |
댓글