인라인 함수: 람다의 부가 비용 없애기
람다가 변수를 포획하면 람다가 생성되는 시점마다 새로운 무명 클래스 객체가 생긴다.
이런경우, 실행 시점에 무명 클래스 생성에 따른 부가 비용이 든다.
→ 똑같은 작업을 수행하는 일반 함수를 사용한 구현보다 덜 효율적
inline 변경자
컴파일러는 그 함수를 호출하는 모든 문장을 함수 본문에 해당하는 바이트 코드로 바꿔치기 해준다.
작동 방식
함수를 호출하는 코드를 함수를 호출하는 바이트코드 대신에 함수 본문을 번역한 바이트 코드로 컴파일한다.
[Kotlin in Action 책]
p.365
8.13~8.3
- synchronized 함수의 본문뿐 아니라 전달된 람다의 본문도 함께 인라이닝 된다.
함수 타입의 변수를 넘길수도 있다.
fun runUnderLock(body: () -> Unit) {
synchronized(lock, body)
}
- 이런 경우에는 람다 본문은 인라이닝 되지않고 synchronized 함수의 본문만 인라이닝 된다.
: 호출하는 코드 위치에서는 변수에 저장된 람다의 코드를 알 수 없기 때문.
인라인 함수의 한계
- 람다식의 본문은 결과코드에 직접 들어갈 수 있다.
- 파라미터로 전달받은 람다를 본문에 사용하는 방식이 한정될 수밖에 없다.
- 파라미터로 받은 람다를 다른변수에 저장하고 사용한다면 람다를 표현하는 객체가 어딘가는 존재해야 하기 때문에 람다를 인라이닝 할 수 없다.
fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
전달받은 transform을 호출하지 않는 대신, 클래스의 생성자에게 그 함수값을 넘긴다.
생성자는 전달 받은 람다를 프로퍼티로 저장한다.
transform을 함수 인터페이스를 구현하는 무명클래스 인스턴스로 만들어야만 한다.
→ 일반적인(인라이닝하지 않는)함수로 표현
noinline 변경자 9.2.4에서 좀더 나옴!
인라이닝 하면 안되는 람다를 파라미터로 받을 때 사용
inline fun foo(inlined: () -> Unit, noinline noInlined: () -> Unit) {
}
컬렉션 연산 인라이닝
- 표준 라이브러리의 컬렉션 함수는 대부분 람다를 인자로 받는다.
- ex) filter 함수는 인라인 함수다
- filter함수의 바이트코드는 그 함수에 전달된 람다 본문의 바이트코드와 함께 filter를 호출한 위치에 들어간다.
: 결과 직접짠 코드와 거의 같다. → 코틀린이 제공하는 함수 인라이닝을 믿고 성능에 신경 쓰지 않아도 된다.
filter와 map을 연쇄해서 사용하면?
- 둘다 인라인 함수다. 추가 객체나 클래스 생성은 없다.
- 하지만 리스트를 걸러낸 결과를 저장하는 중간 리스트를 만든다.
- filter함수에서 만들어진 코드는 원소를 중간리스트에 추가하고, map함수에서 만들어진 코드는 그 중간 리스트를 읽어서 사용한다.
처리할 원소가 많아지면 부가 비용이 커진다.
- asSequence를 통해 중간 리스트로 인한 부가 비용을 줄일 수 있다.
- 중간 시퀀스는 람다를 필드에 저장하는 객체로 표현
- 최종 연산은 중간 시퀀스에 있는 여러 람다를 연쇄 호출한다.
→ 시퀀스는(람다를 저장해야 하므로) 람다를 인라인 하지 않는다.
그렇다고 모든 컬렉션 연산에 asSequence를 붙여서는 안된다.
- 크기가 작은 컬렉션은 일반 연산이 더 성능이 나을 수 있다.
함수를 인라인으로 선언해야 하는 경우
- 람다를 인자로 받는 함수만 성능이 좋아질 가능성이 높다.
- 일반 함수의 경우 JVM은 이미 강력하게 인라이닝을 지원.
- JVM은 코드 실행을 분석해서 가장 이익이 되는 방향으로 호출을 인라이닝 한다.
- JVM의 최적화를 활용한다면 바이트코드에서는 각 함수구현이 정확히 한 번만 있으면 되고, 호출하는 부분에서 따로 함수 코드를 중복할 필요가 없다.
- 반면 코틀린 인라인 함수는 바이트코드에서 각 함수 호출 지점을 함수 본문으로 대치하기 때문에 코드 중복이 생긴다.
- 람다를 인자로 받는 함수를 인라이닝하면 이익이 더 많다
- 인라이닝을 통해 없앨 수 있는 부가 비용이 상당하다.
- 함수 호출 비용을 줄일 수 있다.
- 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체를 만들 필요도 없어진다.
- JVM은 함수 호출과 람다를 인라이닝해 줄 정도로 똑똑하지 못하다.
- 일반 람다에서는 사용할 수 없는 몇가지 기능을 사용할 수 있다.
- 넌로컬(non-local) 반환 - 8장 뒤에서 나옴!
- 인라이닝을 통해 없앨 수 있는 부가 비용이 상당하다.
주의 : 코드 크기에 주의.
- 함수가 크면 바이트 코드가 전체적으로 아주 커질 수 있다
- 다른 inline 함수를 보면 모두 크기가 작다.
자원 관리를 위해 인라인 된 람다 사용
람다로 중복을 없앨 수 있는 일반적인 패턴중 한 가지는 어떤 작업을 하기 전에 자원을 획득하고 작업을 마친 후 자원을 해제하는 자원 관리다.
- 자원(resource)은 파일, 락, 데이터베이스 트랜잭션 등
- 코틀린 표준라이브러리에는, 자바 try-with-resource와 같은 기능을 제공하는 use라는 함수가 있음.
use함수는 Closeable을 구현한 객체에 한해서만 사용할 수 있다.
use는 람다를 호출한 다음에 자원을 닫아준다.
- 람다안에서 예외가 발생한 경우에도 자원을 확실히 닫는다.
'코틀린 & Java > 코틀린인액션' 카테고리의 다른 글
[Kotlin] 클래스, 객체, 인터페이스에 대해 알아보자(1) (0) | 2024.03.18 |
---|---|
[Kotlin] 클래스, 객체, 인터페이스에 대해 알아보자 (2) (0) | 2024.03.18 |
고차 함수 정의 (0) | 2023.05.23 |
delegated property 프로퍼티 접근자 로직 재활용 (0) | 2023.05.09 |
Kotlin 구조 분해 선언과 component 함수(Pair, Triple) (0) | 2023.05.09 |