[Kotlin] Inline modifier

머핀조아·2023년 3월 19일
2

Kotlin

목록 보기
1/2
post-thumbnail

Inline Modifier

컴파일 시점에 '함수를 호출하는 부분'을 '함수의 본문'으로 대체하는 것
// Usage: repeat(4) { println("(${it}) Hello world!") }
inline fun repeat(times: Int, action: (Int) -> Unit) {
	for (index in 0 until times) {
    	action(index)
    }
}

Inline 한정자의 장점

  1. type argument에 refied 한정자를 붙여 사용 가능
  2. 함수 타입 파라미터를 가진 함수가 훨씬 빠르게 동작
  3. 비지역적 리턴(non-local return)이 사용 가능

type argument에 refied 한정자를 붙여 사용 가능

Type parameter가 generic type으로 선언된 함수의 경우 generic type erase로 인해, 함수 내부에서 type argument에 대한 참조를 할 수 없는 경우가 있다. 이를 해결하려면 inline과 refied 한정자를 사용하여 컴파일 시점에 type parameter 부분이 type argument로 대체되어 참조 가능하다.

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

컴파일 시점에 함수의 호출부가 본문부로 대체되기 때문에, 함수 호출과 리턴을 하는 과정(backtrace)이 생략된다. 하지만 이 부분에서 성능의 큰 차이가 발생하지 않고, 실제로 큰 차이가 나는 부분은 함수 타입 파라미터를 가진 경우이다.

Kotlin에서 lambda 표현식은 익명의 클래스로 컴파일되며, lambda 호출 시 익명 객체를 생성하여 빈 함수를 호출한다. 결국 inline으로 호출되지 않는 lambda는 불필요한 객체를 생성하고, 그 객체의 빈 함수를 호출하는 동작방식으로 성능 차이를 유발한다.

비지역적 리턴(non-local return)이 사용 가능

함수 리터럴이 컴파일될 때, 함수가 객체로 래핑되기 때문에 return을 통해 호출부로 돌아올 수 없다. 하지만 inline을 사용하면 해당 호출부로 함수 본문이 대체되기 때문에 호출부에서 return 될 수 있다.


Inline 한정자의 단점

  1. 재귀적으로 호출 시 무한하게 대체될 수 있음
  2. public inline 함수 내부에서는 private, internal 함수 및 프로퍼티 사용 불가
  3. 남용 시 코드의 크기가 쉽게 커질 수 있음

Inline과 함께 쓰이는 한정자

  • crossinline : argument로 인라인 함수를 받지만, 비지역적 리턴을 하는 함수는 불가
  • noinline : argument로 인라인 함수 불가

Inline, Lambda, 그리고 asSequence의 효율

Collection, Sequence의 동작 방식

  • Collection 동작 방식(Eager evaluation)
  • Sequnece 동작 방식(Lazy evaluation)

Kotlin에서 컬렉션을 사용할 때, 일반적으로 sequence로 변환하여 로직을 전개하면 성능적으로 이점을 챙길 수 있다. 그 이유는 위의 표에 나와있는 것 처럼 지연 평가(lazy evaluation)로 인해 중간 단계의 컬렉션을 생성하지 않으며, 결과적으로 필요한 최소한을 연산한다. 따라서, 컬렉션 전체를 기반으로 처리해야 하는 연산(sorted)을 제외하고는 시퀀스를 사용하는 것이 효율적일 것이다. 항상 그럴까?


Collection, Sequence의 실제 구현

// _Collections.map
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

// _Sequences.map
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
    return TransformingSequence(this, transform)
}

Collection에서 map을 할 때에는 inline 함수를 사용하고, sequence의 경우는 그렇지 않다. collection에서는 컴파일 시점에 실제 구현부가 대체되고, sequence에서는 위에서 다루었던 내용처럼 익명 객체를 생성하여 동작한다는 것이다. 결국, 다루는 컬렉션의 크기가 작은 경우에는 sequence보다 collection을 사용하는 것이 효율적이다.

profile
채찍질이 필요한 개발자

0개의 댓글