관리 메뉴

나만을 위한 블로그

[이펙티브 코틀린] 아이템 46. 함수 타입 파라미터를 갖는 함수에 inline 한정자를 붙여라 본문

책/Effective Kotlin

[이펙티브 코틀린] 아이템 46. 함수 타입 파라미터를 갖는 함수에 inline 한정자를 붙여라

참깨빵위에참깨빵 2023. 5. 5. 01:44
728x90
반응형

코틀린 표준 라이브러리의 고차 함수를 보면 대부분 inline 한정자가 붙어 있다. inline 한정자의 역할은 컴파일 시점에 '함수를 호출하는 부분'을 '함수의 본문'으로 대체하는 것이다. inline 한정자를 붙여 함수를 만들면 큰 변화가 일어난다.

일반적인 함수를 호출하면 함수 본문으로 점프하고 본문의 모든 문장을 호출한 뒤에 함수를 호출했던 위치로 다시 점프하는 과정을 거친다. 하지만 '함수를 호출하는 부분'을 '함수 본문'으로 대체하면 이런 점프가 일어나지 않는다. inline 한정자를 쓰면 아래의 장점이 있다.

 

  • 타입 아규먼트에 reified 한정자를 붙여 사용할 수 있다
  • 함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작한다
  • 비지역(non-local) 리턴을 사용할 수 있다

 

단점도 존재한다. inline 한정자를 붙였을 때 발생하는 비용도 당연히 있다. 아래부터 inline 한정자의 장단점을 확인한다.

 

타입 아규먼트를 reified로 쓸 수 있다

 

JVM 바이트 코드에는 제네릭이 없다. 따라서 컴파일하면 제네릭 타입과 관련된 내용이 제거된다. List<Int>를 컴파일하면 List로 바뀐다. 그래서 객체가 리스트인지 확인하는 코드는 쓸 수 있지만 List<Int>인지 확인하는 코드는 쓸 수 없다. 같은 이유로 같은 타입 파라미터에 대한 연산도 오류가 발생한다.

함수를 인라인으로 만들면 이런 제한을 무시할 수 있다. 함수 호출이 본문으로 대체되므로 reified 한정자를 지정하면 타입 파라미터를 사용한 부분이 타입 아규먼트로 대체된다.

 

fun main() {
    printTypeName<Int>()
    printTypeName<Char>()
    printTypeName<String>()
}
// Int
// Char
// String

inline fun <reified T> printTypeName() = println(T::class.simpleName)

 

컴파일하는 동안 printTypeName()의 본문이 실제로 대체된다. reified는 굉장히 유용한 한정자다.

 

함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작한다

 

모든 함수는 inline 한정자를 붙이면 좀 더 빠르게 동작한다. 함수 호출, 리턴을 위해 점프하는 과정과 백스택을 추적하는 과정이 없기 때문이다. 그래서 표준 라이브러리에 있는 간단한 함수들에는 대부분 inline 한정자가 붙어 있다.

하지만 함수 파라미터를 갖지 않는 함수에선 이런 차이가 큰 성능 차이를 발생시키지 않는다. JVM에서 아규먼트가 같은 함수 타입은 Function0 타입으로 변환된다. 아이템 45에서처럼 함수 본문을 객체로 wrap하면 코드 속도가 느려진다.

인라인 함수와 인라인 함수가 아닌 함수의 더 중요한 차이는 함수 리터럴 내부에서 지역 변수를 캡쳐할 때 확인할 수 있다. 캡쳐된 값은 객체로 래핑해야 하며 쓸 때마다 객체를 통해 작업이 이뤄져야 한다.

 

inline 한정자의 비용

 

inline 한정자는 유용한 한정자지만 모든 곳에 쓸 수는 없다. 인라인 함수는 재귀적으로 동작할 수 없다. 재귀적으로 쓰면 무한하게 대체되는 문제가 발생한다. 이런 문제는 인텔리제이가 오류로 잡아주지 못하므로 굉장히 위험하다.

또한 인라인 함수는 더 많은 가시성 제한을 가진 요소를 쓸 수 없다. public 인라인 함수 안에서는 private, internal 가시성을 가진 함수, 프로퍼티를 쓸 수 없다. 인라인 함수는 구현을 숨길 수 없으므로 클래스에 거의 사용하지 않는다.

 

crossinline, noninline

 

함수를 인라인으로 만들고 싶지만 어떤 이유로 일부 함수 타입 파라미터는 inline으로 받고 싶지 않은 경우가 있을 수 있다. 이런 경우 아래 한정자를 사용한다.

 

  • crossinline : 아규먼트로 인라인 함수를 받지만 비지역적 리턴을 하는 함수는 받을 수 없게 만든다. 인라인으로 만들지 않은 다른 람다식과 조합해서 쓸 때 문제가 발생하는 경우 활용한다
  • noninline : 아규먼트로 인라인 함수를 받을 수 없게 만든다. 인라인 함수가 아닌 함수를 아규먼트로 쓰고 싶을 때 활용한다

 

두 한정자의 의미를 확실하게 기억하면 좋지만 인텔리제이가 필요할 때 알아서 제안해 주므로 대충 알아둬도 괜찮다.

반응형
Comments