Languages

kotlin의 inline 함수 (고차함수, synchronized)

!쪼렙조햄 2020. 5. 26. 18:59
반응형

8.2 인라인 함수 : 람다의 부가 비용 없애기

  • 람다를 사용하는 구현은 똑같은 작업을 수행하는 일반 함수를 사용한 구현보다 덜 효율적이다
    • 람다가 변수를 포획하면 람다 생성때마다 새로운 무명 클래스 객체 생성
    • 반복되는 코드를 별도의 함수로 빼내기 + 효율적인 코드는 없을까? --> inline 함수!

8.2.1 인라이닝이 작동하는 방식

  • inline 변경자를 함수에 붙이면 컴파일러는 그 함수를 호출하는 모든 문장을 함수 본문에 해당하는 바이트 코드로 바꿔준다
inline fun <T> synchronized(lock: Lock, action: () -> T): T{
    lock.lock()
    try{
        return action()
    }
    finally {
        lock.unlock()
    }
}
val l = Lock()
synchronized(l){
    //~
}
  • 다중 스레스 환경에서 공유 자원의 동시접근을 막기위한 코드
    • 위 코드의 synchronized는 Lock 클래스의 인스턴스를 요구한다
    • java에서는 임의의 객체에 대해 synchronized를 사용할 수 있다
    • 위 함수를 inline으로 선언했으므로 synchronized를 호출하는 코드는 모두 자바의 synchronized문과 같아진다
fun foo(l : Lock){
    println("Before sync")
    synchronized(l){
        println("Action")
    }
    println("After sync")
}
  • 위와 같이 코드를 작성하면 아래 코드와 동등한 코드가 된다
fun __foo__(l: Lock){
    println("Before sync")
    l.lock()
    try{
        println("Action")
    }finally{
        l.unlock()
    }
    println("After sync")
}
  • synchronized 함수의 본문 뿐 아니라, synchronized에 전달된 람다의 본문도 함께 inline 된다
    • 람다의 본문에 의해 만들어지는 바이트코드는 그 람다를 호출하는 코드 정의의 일부분으로 간주되기 때문에 코틀린 컴파일러는 그 람다를 함수 인터페이스를 구현하는 무명클래스로 감싸지 않는다
class LockOwner(val lock : Lock){
    fun runUnderLock(body : () -> Unit){
        synchronized(lock, body)
    }
}
  • 위와 같은 경우에는 synchronized 함수를 호출할때 body의 람다 코드를 알수없기 때문에 synchronized 함수의 본문만 inline 된다

8.2.2 인라인 함수의 한계

  • 함수 inline 되면 그 함수에 전달된 람다식의 본문은 결과 코드에 직접 들어갈 수 있다
    • 람다가 본문에 직접 펼쳐지기 때문에 파라미터로 받은 함수를 본문에 사용하는 방식이 한정된다
    • 파라미터로 받은 람다를 다른 변수에 저장하고, 그 변수를 나중에 사용한다면 이 람다를 인라이닝 할 수 없다
fun <T,R> Sequence<T>.map(transform : (T) -> R):Sequence<R>{
    return TransformingSequence(this, transform)
}
  • 위와 같은 경우, transform에 람다식을 받아서 Sequence를 변환시켜서 반환해주는 코드
    • transform 파라미터로 전달받은 함수값을 호출하지 않고, TransformingSequence 클래스의 생성자에 함수 값을 넘긴다
    • 이런 경우에는 transform을 inline 시킬수없다. 함수 인터페이스를 구현하는 무명클래스 인스턴스로 만들어야만 한다

8.2.3 컬렉션 연산 인라이닝

  • kotlin의 filter, map 함수는 인라인 함수
    • filter, map 은 결과를 저장하는 중간 리스트들을 만든다 (부가비용이 커진다)
    • Sequence를 사용하면 중간리스트를 사용하는데 드는 부가비용은 줄어든다
      • Sequence는 람다를 저장해야 하므로 람다를 inline하지 않는다
      • 그래서 오히려 크기가 작은 collection은 성능이 나쁠수도있다
    • 크기가 큰 collection -> Sequence 사용
    • 크기가 작은 collection -> inline 사용한 일반 컬렉션 사용이 나을수도 있다

8.2.4 함수를 인라인으로 선언해야 하는 경우

  • inline 키워드를 사용해도 람다를 인자로 받는 함수만 성능이 좋아질 가능성이 높다
  • 일반 함수 호출의 경우 JVM은 이미 강력하게 인라이닝을 지원한다
    • JVM은 코드 실행을 분석해서 가장 이익이 되는 방향으로 호출을 인라이닝 한다
  • 람다를 인자로 받는 함수를 인라이닝하면 이익이 많다
    • 함수 호출비용 감소
    • 람다를 표현하는 클래스, 람다에 해당하는 객체 만드는 비용 감소
  • 코드 크기자체가 너무 크면 inline 지양

8.2.5 자원 관리를 위해 인라인된 람다 사용

  • 앞서본 synchronized 처럼 자원 관리를 위한 메소드들이 있다
    • withLock
      • synchronized와 같은 기능
    • use
      • 자바의 try-with-resource와 같은 기능
반응형