본문 바로가기
Kotlin

[Kotlin] Kotlin 공식 문서 번역 - 인라인 함수 (Inline functions)

by 노력남자 2023. 8. 12.
반응형

인라인 함수 (Inline functions)

 

고차 함수를 사용하면 특정 런타임 오버헤드가 발생합니다. 각 함수는 객체이며, 클로저를 캡처합니다. 클로저는 함수 본문에서 접근할 수 있는 변수의 범위입니다. 메모리 할당(함수 객체 및 클래스 모두)과 가상 호출은 런타임 오버헤드를 도입합니다.

하지만 많은 경우에 이러한 종류의 오버헤드는 람다 표현식을 인라인화하여 제거할 수 있습니다. 아래에 표시된 함수는 이러한 상황의 좋은 예시입니다. lock() 함수는 호출 위치에서 쉽게 인라인화될 수 있습니다. 다음과 같은 경우를 고려해보세요:

 

lock(l) { foo() }

 

매개변수에 대한 함수 객체를 생성하고 호출을 생성하는 대신, 컴파일러는 다음과 같은 코드를 생성할 수 있습니다:

 

l.lock()
try {
    foo()
} finally {
    l.unlock()
}

 

컴파일러가 이를 수행하도록 하려면 lock() 함수에 inline 한정자를 지정하세요:

 

inline fun <T> lock(lock: Lock, body: () -> T): T { ... }

 

inline 한정자는 함수 자체와 해당 함수에 전달되는 람다 모두에 영향을 미칩니다. 이 모두가 호출 위치로 인라인화됩니다.

인라인화는 생성된 코드의 크기를 증가시킬 수 있습니다. 그러나 합리적인 방식으로 수행하면(큰 함수의 인라인화 피하기), 특히 루프 내부의 "메가모픽(megamorphic)" 호출 위치에서 성능이 향상될 것입니다.

 

noinline

 

인라인 함수에 전달되는 모든 람다를 인라인화하지 않으려면 함수 매개변수 중 일부에 noinline 한정자를 지정하세요:

 

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }

 

인라인 가능한 람다는 인라인 함수 내부에서만 호출하거나 인라인 가능한 인수로 전달할 수 있습니다. 그러나 noinline 람다는 필드에 저장되거나 전달되는 등 모든 방법으로 조작할 수 있습니다.

인라인 함수에 인라인 가능한 함수 매개변수와 reified 형식 매개변수가 없는 경우, 컴파일러는 경고를 발생시킬 것입니다. 이는 해당 함수를 인라인화하는 것이 크게 유용하지 않을 가능성이 높기 때문입니다(인라인화가 필요하다고 확신하는 경우에는 @Suppress("NOTHING_TO_INLINE") 주석을 사용하여 경고를 억제할 수 있습니다).

 

Non-local returns

 

Kotlin에서는 기명 함수나 익명 함수에서 일반적인 미지정 반환만 사용하여 함수를 종료할 수 있습니다. 람다에서 종료하려면 레이블을 사용하세요. 람다 내에서 벌어지는 반환은 미지정 함수를 반환할 수 없기 때문에 허용되지 않습니다:

 

fun foo() {
    ordinaryFunction {
        return // 오류: `foo`를 여기서 반환할 수 없음
    }
}

 

그러나 람다가 전달되는 함수가 인라인화되면 반환도 인라인화될 수 있습니다. 따라서 다음과 같이 허용됩니다:

 

fun foo() {
    inlined {
        return // OK: 람다가 인라인화됨
    }
}

 

이러한 반환(람다에 위치한 것이지만 둘러싸는 함수를 종료함)은 비지역 반환이라고 합니다. 이러한 종류의 구조는 일반적으로 루프에서 발생하며, 인라인 함수가 종종 둘러싸게 됩니다:

 

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // hasZeros에서 반환
    }
    return false
}

 

일부 인라인 함수는 매개변수로 전달된 람다를 함수 본문에서 직접 호출하는 대신 로컬 객체나 중첩된 함수와 같은 다른 실행 컨텍스트에서 호출할 수도 있습니다. 이러한 경우 람다 내에서 비지역 제어 흐름도 허용되지 않습니다. 인라인 함수의 람다 매개변수가 비지역 반환을 사용할 수 없음을 나타내려면 람다 매개변수에 crossinline 한정자를 지정하세요:

 

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

 

break와 continue는 아직 인라인화된 람다에서 사용할 수 없지만, 미래에 지원할 계획입니다.

 

실수체화 형식 매개변수 (Reified type parameters)


때로는 매개변수로 전달된 형식에 액세스해야 할 때가 있습니다:

 

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

 

여기서 트리를 거슬러 올라가며 반사(reflection)를 사용하여 노드가 특정 유형을 가지는지 확인합니다. 이 모두 잘 작동하지만 호출 위치는 아주 깔끔하지 않습니다:

 

treeNode.findParentOfType(MyTreeNode::class.java)

 

더 나은 해결책은 이 함수에 단순히 형식을 전달하는 것입니다. 다음과 같이 호출할 수 있습니다:

treeNode.findParentOfType<MyTreeNode>()

 

이를 가능하게 하기 위해 인라인 함수는 실수체화(reified) 형식 매개변수를 지원합니다. 따라서 다음과 같이 작성할 수 있습니다:

 

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

 

위 코드는 형식 매개변수를 reified 한정자로 한정하여 함수 내에서 일반 클래스처럼 액세스할 수 있도록 합니다. 함수가 인라인화되므로 반사(reflection)가 필요하지 않으며, !is 및 as와 같은 일반 연산자를 사용할 수 있습니다. 또한 위와 같이 함수를 호출할 수 있습니다: myTree.findParentOfType<MyTreeNodeType>().

많은 경우 반사가 필요하지 않을 수 있지만 reified 형식 매개변수와 함께 사용할 수도 있습니다:

 

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

 

인라인화되지 않은 일반 함수에서는 실수체화된 매개변수를 가질 수 없습니다. 실행 시간 표현을 갖지 않는 형식(예: 실수체화되지 않는 형식 매개변수 또는 Nothing과 같은 가상 형식)은 실수체화된 형식 매개변수의 인수로 사용할 수 없습니다.

 

인라인 속성 (Inline properties)

 

인라인 한정자는 백킹 필드가 없는 속성의 접근자에 사용할 수 있습니다. 개별 속성 접근자에 주석을 달 수 있습니다:

 

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

 

또한 전체 속성에 주석을 달 수 있으며, 이렇게 하면 두 접근자 모두를 인라인으로 표시합니다:

 

inline var bar: Bar
    get() = ...
    set(v) { ... }

 

호출 위치에서 인라인 접근자는 일반 인라인 함수처럼 인라인화됩니다.

 

공개 API 인라인 함수의 제한 사항 (Restrictions for public API inline functions)

 

인라인 함수가 공개(public)나 보호된(protected) 상태이지만 개인(private) 또는 내부(internal) 선언의 일부가 아닐 경우, 해당 인라인 함수는 모듈의 공개 API로 간주됩니다. 이는 다른 모듈에서 호출되며 해당 호출 위치에서도 인라인화됩니다.

이로 인해, 인라인 함수를 선언한 모듈의 변경으로 인해 호출하는 모듈이 해당 변경 이후 재컴파일되지 않는 경우 이진 호환성과 관련된 일부 위험이 발생할 수 있습니다.

모듈의 비공개 API의 변경으로 인해 이러한 호환성 위험을 없애려면 공개 API 인라인 함수에서는 비공개 API 선언(즉, private 및 internal 선언 및 해당 부분)을 본문에서 사용할 수 없습니다.

내부 선언은 @PublishedApi로 주석을 달 수 있으며, 이를 통해 해당 선언을 공개 API 인라인 함수에서 사용할 수 있습니다. 내부 인라인 함수가 @PublishedApi로 표시되면 해당 본문도 마치 공개된 것처럼 확인됩니다.

 

 

원문

 

https://kotlinlang.org/docs/inline-functions.html

반응형

댓글