Development Tip

Kotlin에서 인라인 함수를 언제 사용합니까?

yourdevel 2020. 10. 8. 19:06
반응형

Kotlin에서 인라인 함수를 언제 사용합니까?


인라인 함수가 성능을 향상시키고 생성 된 코드를 증가시킬 수 있다는 것을 알고 있지만 언제 올바르게 사용하는지 확실하지 않습니다.

lock(l) { foo() }

매개 변수에 대한 함수 객체를 생성하고 호출을 생성하는 대신 컴파일러는 다음 코드를 내 보냅니다. ( 출처 )

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

하지만 인라인이 아닌 함수에 대해 kotlin이 만든 함수 개체가 없다는 것을 발견했습니다. 왜?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}

유형의 람다 () -> Unit(매개 변수 없음, 반환 값 없음) 를 취하고 다음과 같이 실행하는 고차 함수를 생성한다고 가정 해 보겠습니다 .

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

Java 용어로 이것은 다음과 같이 번역됩니다 (간체!).

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

그리고 Kotlin에서 전화하면 ...

nonInlined {
    println("do something here")
}

Function내부적으로 람다 안에 코드를 래핑 하는의 인스턴스가 여기에 생성됩니다 (다시 말하지만 이것은 단순화 됨).

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

따라서 기본적으로이 함수를 호출하고 람다를 전달하면 항상 Function객체 의 인스턴스가 생성됩니다 .


반면에 inline키워드 를 사용하는 경우 :

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

다음과 같이 호출하면 :

inlined {
    println("do something here")
}

Function인스턴스가 생성 되지 않고 대신 block인라인 함수 내부 의 호출 주변의 코드 가 호출 사이트에 복사되므로 바이트 코드에서 다음과 같은 내용을 얻을 수 있습니다.

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

이 경우 새 인스턴스가 생성되지 않습니다.


추가하겠습니다 : "사용하지 않을 때 inline" :

1) 다른 함수를 인수로 받아들이지 않는 간단한 함수가있는 경우 인라인하는 것은 의미가 없습니다. IntelliJ는 다음과 같이 경고합니다.

인라인 '...'의 예상 성능 영향은 미미합니다. 인라인은 함수 유형의 매개 변수가있는 함수에 가장 적합합니다.

2) "함수 유형의 매개 변수가있는"함수가 있더라도 인라인이 작동하지 않는다는 컴파일러를 만날 수 있습니다. 이 예를 고려하십시오.

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

이 코드는 오류와 함께 컴파일되지 않습니다.

'...'에서 인라인 매개 변수 '연산'을 잘못 사용했습니다. 매개 변수 선언에 'noinline'수정자를 추가합니다.

The reason is that the compiler is unable to inline this code. If operation is not wrapped in an object (which is implied by inline since you want to avoid this), how can it be assigned to a variable at all? In this case, the compiler suggests making the argument noinline. Having an inline function with a single noinline function does not make any sense, don't do that. However, if there are multiple parameters of functional types, consider inlining some of them if required.

So here are some suggested rules:

  • You can inline when all functional type parameters are called directly or passed to other inline function
  • You should inline when ^ is the case.
  • You cannot inline when function parameter is being assigned to a variable inside the function
  • You should consider inlining if at least one of your functional type parameters can be inlined, use noinline for the others.
  • You should not inline huge functions, think about generated byte code. It will be copied to all places the function is called from.
  • Another use case is reified type parameters, which require you to use inline. Read here.

The most important case when we use inline modifier is when we define util-like functions with parameter functions. Collection or string processing (like filter, map or joinToString) or just standalone functions are perfect example.

This is why inline modifier is mostly an important optimization for library developers. They should know how does it work and what are its improvements and costs. We will use inline modifier in our projects when we define our own util functions with function type parameters.

When we don’t have function type parameter, reified type parameter, and we don’t need non-local return, then we most likely shouldn’t use inline modifier. This is why we will have a warning on Android Studio or IDEA IntelliJ.

Also there is the code size problem. Inlining a large function could dramatically increase the size of the bytecode because it's copied to every calls site. In such cases, you can refactor the function and extract code to regular functions.


One simple case where you might want one is when you create a util function that takes in a suspend block. Consider this.

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

In this case, our timer won't accept suspend functions. To solve it, you might be tempted to make it suspend as well

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

But then it can only be used from coroutines/suspend functions itself. Then you'll end up making an async version and a non-async version of these utils. The problem goes away if you make it inline.

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

Here is a kotlin playground with the error state. Make the timer inline to solve it.

참고URL : https://stackoverflow.com/questions/44471284/when-to-use-an-inline-function-in-kotlin

반응형