[kotlin] higher Older functions, inline
Higher-order funtions, Inline
인라인 함수란 무엇일까? 인라인 함수는 컴파일러가 함수 호출부에 함수 본문(내용)을 삽입하는 것이다. 바이트코드를 삽입하는 것이다.
그렇다면 왜 쓰는 것일까? 고차 함수를 사용하면 런타임 패널티가 있기때문에 함수 구현 자체를 코드 내부에 넣음으로써 오버헤드를 없앨 수 있다. Document에 있는 글이다. 즉, 고차 함수의 런타임 패널티를 없애기 위해서 쓴다. 그렇다면 고차 함수가 무엇인지 알아보는게 순서인거 같다.
Higher-order functions
고차함수는 함수를 파라미터로 받거나 혹은 함수를 리턴하는 함수이다. 예를 들어,
fun <T> ArrayList<T>.filterOnCondition(condition: (T) -> Boolean): ArrayList<T>{
val result = arrayListOf<T>()
for (item in this){
if (condition(item)){
result.add(item)
}
}
return result
}
이런 코드가 있다. condition: (T) -> Boolean 부분을 보자. 이 말은 현재 확장하고 있는 주체인 T를 파라미터로 받고 Boolean값을 리턴한다는 뜻이다. 이게 고차함수 함수이다.
조금 더 예를 들어보자.
fun isMultipleOf (number: Int, multipleOf : Int): Boolean{
return number % multipleOf == 0
}
var list = arrayListOf<Int>()
for (number in 1..10){
list.add(number)
}
var resultList = list.filterOnCondition { isMultipleOf(it, 5) }
이런 코드가 있다. 이 코드르 아래와 같이 줄일 수 있다.
var resultList = list.filterOnCondition { it -> it % 5 == 0 }
or
var resultList = list.filterOnCondition { it % 5 == 0 }
간단하게 알아봤다.
그럼 inline함수를 다시 알아보도록 하겠다.
Inline
위에서 고차함수의 런타임 패널티를 없앤다고 하였다. 그렇다면 Inline이 뭔지 간단히 예를 들어서 보자.
fun doSomething() {
print("doSomething start")
doSomethingElse()
print("doSomething end")
}
fun doSomethingElse() {
print("doSomethingElse")
}
이런 코드가 있다. 이 코드의 decompiled된 것을 보자.
public void doSomething() {
System.out.print("doSomething start");
doSomethingElse();
System.out.print("doSomething end");
}
public void doSomethingElse() {
System.out.print("doSomethingElse");
}
즉, doSomething()함수에서 doSomethingElse를 호출하고 있다.
이번에는 inline을 붙여보자.
fun doSomething() {
print("doSomething start")
doSomethingElse()
print("doSomething end")
}
inline fun doSomethingElse() {
print("doSomethingElse")
}
이런 코드가 있다. 이 코드의 decompiled된 것을 보자.
public void doSomething() {
System.out.print("doSomething start");
System.out.print("doSomethingElse");
System.out.print("doSomething end");
}
doSomethingElse의 코드가 doSomething()부분에 카피되어 삽입되어 있는 것을 볼 수 있다. 즉, doSomthing()은 더 이상 doSomethingElse()를 호출하지 않는다. 이게 인라인함수를 붙였을때의 차이이다.
이것이 고차함수와 사용했을 때 왜 이득이 있는지 살펴보자 먼저, inline을 붙이지 말아보자.
fun doSomething() {
print("doSomething start")
doSomethingElse {
print("doSomethingElse")
}
print("doSomething end")
}
fun doSomethingElse(abc: () -> Unit) {
// I can take function
// do something else here
// execute the function
abc()
}
아래는 decompiled된 코드 이다.
public void doSomething() {
System.out.print("doSomething start");
doSomethingElse(new Function() {
@Override
public void invoke() {
System.out.print("doSomethingElse");
}
});
System.out.print("doSomething end");
}
public void doSomethingElse(Function abc) {
abc.invoke();
}
인라인을 붙여보자.
fun doSomething() {
print("doSomething start")
doSomethingElse {
print("doSomethingElse")
}
print("doSomething end")
}
inline fun doSomethingElse(abc: () -> Unit) {
// I can take function
// do something else here
// execute the function
abc()
}
아래는 decompiled된 코드이다
public void doSomething() {
System.out.print("doSomething start");
System.out.print("doSomethingElse");
System.out.print("doSomething end");
}
앞서 살펴본 것과 마찬가지로 호출부에 함수 본문이 삽입되어 있다. 이로써, inline은 function calls를 피할 수 있다. 또한, 새로운 인스턴스 생성도 막을 수 있다.
그래서 inline함수를 쓰면 장점이 뭐가 있을까?
- Function Call 오버헤드가 발생하지 않는다.
- 함수가 호출 될 때, 스택에 push/pop 오버헤드를 save한다.
- 함수의 return call 오버헤드도 save한다.
- 새로운 인스턴스 생성을 방지한다.
언제쓰면 좋을까?
- 고차함수가 사용될 때
- 그러면서, 함수 코드 블럭이 너무 크지 않을때 쓰며 좋을 것 같다.
코드가 너무 커지고 여러곳에 많이 불리면 큰 코드 덩어리가 여러곳에 반복되어 삽입되어지게 된다.
참조 : https://medium.com/@agrawalsuneet/higher-order-functions-in-kotlin-3d633a86f3d7 https://blog.mindorks.com/understanding-inline-noinline-and-crossinline-in-kotlin
다시 살펴보기 : https://blog.kotlin-academy.com/effective-kotlin-consider-inline-modifier-for-higher-order-functions-758afcaffc11