본문 바로가기

Kotlin

Kotlin 의 inline 함수

Kotlin 은 고차함수에 대한 문법을 지원한다. 

고차함수를 사용한 함수는 런타임 오버헤드가 어느정도 발생하는데  이를 줄이기 위한 방법으로 Kotlin 은 Inline Function 을 지원한다.

더보기

고차함수란, 함수를 파라미터로 받거나 함수를 리턴하는 즉, 함수를 일급객체로 취급하는 함수를 말한다.

 

 

고차함수의 오버헤드

class InlineTestClass {

    fun highOrderedFunction(a: Int, actionBlock: (a: Int) -> Int): Int {
        actionBlock(a)
        return a + 1
    }


    fun main() {
        val result = highOrderedFunction(1) { a -> a * 2 }
        println(result)
    }
}

숫자와, 숫자 를 핸들링하는 어떤 함수를 받아서 처리하는 고차함수를 하나 정의해보았다.

위 함수를 컴파일하면, 아래와 같이 매 호출시마다 Function을 생성해서 전달하고, 고차함수 안에서 호출하는 java 코드가 생성된다.

 

 

매번 새 Function 을 생성하는만큼의 오버헤드가 발생하는 것이다.

이를 Inline 키워드를 사용해 해결할 수 있다.

 

 

Inline

class InlineTestClass {

    inline fun highOrderedFunction(a: Int, actionBlock: (a: Int) -> Int): Int {
        actionBlock(a)
        return a + 1
    }


    fun main() {
        val result = highOrderedFunction(1) { a -> a * 2 }
        println(result)
    }
}

기존 고차함수에 Inline 키워드를 부여하고 자바로 컴파일된 클래스를 보면?

 

main 함수로 inline 으로 정의한 고차함수와, 인자로 전달한 함수가 펼쳐져서 인라인 되었다.

이렇게 되면 굳이 매 메서드 호출마다 Function 을 생성하여 메모리를 할당하고 또 메서드 안에서 함수블럭을 실행할 메모리를 할당하는 오버헤드가 사라진다.

 

noinline

inline 함수는 위에서 보았듯, Function 을 생성하지 않고 코드를 펼쳐서 인라인 하는 개념이기 때문에, 

또 다른 함수의 인자로 전달받은 함수를 전달할 수 없다.

이런식으로 전달 받은 인자 함수를 inline 하면 안되는 경우가 있는데, 이럴때 사용하는 것이 noinline 키워드이다.

 

inline 함수에서 전달받은 함수를 다른 함수에 전달하면, 컴파일 오류가 발생한다

    inline fun highOrderedFunction(a: Int, noinline actionBlock: (a: Int) -> Int): Int {
        highOrderedFunction_2(a, actionBlock)
        return a + 1
    }

    fun highOrderedFunction_2(a: Int, actionBlock: (a : Int) -> Int) {
        println(actionBlock(a))
    }

이렇게 하면, 전달받은 함수도 다시 전달할 수 있게 된다.

(하지만 이 경우는 highOrderedFunction 을 inline 함수로 지정할 필요가 없게 되므로 IDE 나 정적분석 프로그램이 경고를 줄 것이다)

함수를 2개 이상 전달 받는 경우, 하나는 인라인하고 하나는 인라인하지 않는등의 방식으로 사용하면 된다.

 

refied

코틀린에는 inline 함수에만 사용할 수 있는 refied 라는 키워드가 있다.

fun <T> generic(c: Class<T>)

원래 제너릭 함수에서 전달받는 타입 T 는 컴파일 타임에는 존재하지만, 런타임에는 Type Eraser 에 의해 존재하지 않아

런타임에서 클래스의 타입에 접근하고 싶다면 Class<T> 를 인자로 넘겨야한다.

 

    fun <T> generic() {
        val a = "ㅋㅋㅋ"
        assertTrue(a.javaClass === T::class.java) // 컴파일 오류
    }

    fun <T> generic(type: Class<T>) {
        val a = "ㅋㅋㅋ"
        assertTrue(a.javaClass === type) // true
    }

 

inline fun <reified T> genericFunc()

하지만 inline 함수에서는 refied 키워드를 사용하여, 타입파라미터를 넘길 필요 없이, 함수 body 에서 T 타입에 대해 접근할 수 있다.

또한 is 키워드로 타입비교가 가능하다.

    inline fun <reified T> generic() {
        val a = "ㅋㅋㅋ"
        assertTrue(a is T) // no compile error, True
    }

위 코드는 아래와 같이 컴파일된다.

kotlin.jvm.internal 패키지에 존재하는 함수를 호출한다.

reified 타입 파라미터로 작성된 인라인 함수는 자바코드에 호출할 수 없다.