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


105

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

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();
    }
}

7
이를위한 두 가지 주요 사용 사례가 있습니다. 하나는 특정 유형의 고차 함수를 사용하는 것이고 다른 하나는 수정 된 유형 매개 변수입니다. 인라인 함수의 문서는 다음을 다룹니다. kotlinlang.org/docs/reference/inline-functions.html
zsmb13

2
@ zsmb13 감사합니다. 그러나 나는 그것을 이해하지 못한다 : "매개 변수에 대한 함수 객체를 생성하고 호출을 생성하는 대신 컴파일러는 다음 코드를 방출 할 수있다"
holi-java

2
나는 그 예도 tbh를 얻지 못한다.
filthy_wizard

답변:


279

유형의 람다 () -> 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");

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


19
처음에 Function 객체 래퍼를 사용하면 어떤 이점이 있습니까? 즉-왜 모든 것이 인라인되지 않습니까?
아르투르 Vancans

14
당신은 또한 임의의 등, 매개 변수로 주위에 기능을 전달 변수에 저장할 수 있습니다이 방법
zsmb13

6
에 의해 위대한 설명 @ zsmb13
Yajairo87

2
할 수 있으며 복잡한 작업을 수행하면 결국 noinlinecrossinline키워드 에 대해 알고 싶을 것 입니다. 문서를 참조하십시오 .
zsmb13

2
문서는 기본적으로 인라인하기를 원하지 않는 이유를 제공합니다. 인라인으로 인해 생성 된 코드가 커질 수 있습니다. 그러나 우리가 합리적인 방식으로 (즉, 큰 함수를 인라인하는 것을 피하는) 경우, 성능, 특히 루프 내부의 "거 대형"호출 사이트에서 보상을받을 것입니다.
CorayThan

43

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

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

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

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

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

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

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

그 이유는 컴파일러가이 코드를 인라인 할 수 없기 때문입니다. operation객체에 래핑되지 않은 경우 (이를 inline피하고 싶기 때문에에 의해 암시 됨 ), 어떻게 변수에 할당 할 수 있습니까? 이 경우 컴파일러는 인수를 제안합니다 noinline. 갖는 inline하나와 기능을noinline함수가 함수 말이되지 않습니다. 그렇게하지 마십시오. 그러나 함수 유형의 매개 변수가 여러 개인 경우 필요한 경우 일부 매개 변수를 인라인하는 것이 좋습니다.

따라서 다음은 몇 가지 제안 된 규칙입니다.

  • 모든 함수형 매개 변수가 직접 호출되거나 다른 인라인 함수로 전달 될 때 인라인 할 수 있습니다.
  • 당신은 해야한다 ^ 사건 때 인라인.
  • 당신 은 할 수 없습니다 기능 매개 변수가 함수 내에서 변수에 할당 될 때 인라인
  • 당신은 해야 당신의 기능 유형 매개 변수 중 적어도 하나는, 사용을 인라인 할 수있는 경우 인라인 고려해 noinline다른 사람을 위해.
  • 당신은 인라인 큰 기능은 생성 된 바이트 코드에 대해 생각합니다. 함수가 호출 된 모든 위치에 복사됩니다.
  • 또 다른 사용 사례는 reified유형 매개 변수로 inline. 여기에서 읽으 십시오 .

4
기술적으로는 람다 식을 제대로 사용하지 않는 함수를 인라인 할 수 있습니까? .. 여기서 이점은이 경우 함수 호출 오버 헤드가 방지된다는 것입니다. 스칼라와 같은 언어는 이것을 허용합니다. ing
rogue-one

3
@ rogue-one Kotlin은 이번 인라이닝을 금지하지 않습니다. 언어 저자들은 단순히 성능상의 이점이 미미할 것이라고 주장하고 있습니다. 작은 메소드는 특히 자주 실행되는 경우 JIT 최적화 중에 JVM에 의해 이미 인라인 될 가능성이 있습니다. inline해로울 수있는 또 다른 경우 는 다른 조건부 분기와 같이 인라인 함수에서 기능 매개 변수가 여러 번 호출되는 경우입니다. 이 때문에 기능적 인수에 대한 모든 바이트 코드가 중복되는 경우가 발생했습니다.
Mike Hill

5

인라인 수정자를 사용할 때 가장 중요한 경우는 매개 변수 함수로 유틸리티와 유사한 함수를 정의 할 때입니다. 컬렉션 또는 문자열 처리 ( filter, map또는 joinToString) 또는 독립형 함수가 완벽한 예입니다.

이것이 인라인 수정자가 대부분 라이브러리 개발자에게 중요한 최적화 인 이유입니다. 그들은 그것이 어떻게 작동하는지, 그리고 그것의 개선과 비용이 무엇인지 알아야합니다. 함수 유형 매개 변수로 자체 util 함수를 정의 할 때 프로젝트에서 인라인 수정자를 사용해야합니다.

함수 유형 매개 변수, 수정 된 유형 매개 변수가없고 비 로컬 리턴이 필요하지 않은 경우 인라인 수정자를 사용하지 않아야합니다. 이것이 Android Studio 또는 IDEA IntelliJ에 대한 경고가 표시되는 이유입니다.

또한 코드 크기 문제가 있습니다. 큰 함수를 인라인하면 모든 호출 사이트에 복사되기 때문에 바이트 코드의 크기가 크게 증가 할 수 있습니다. 이러한 경우 함수를 리팩터링하고 코드를 일반 함수로 추출 할 수 있습니다.


4

고차 함수 는 매우 유용하며 실제로 reusability코드를 향상시킬 수 있습니다 . 그러나 사용에 대한 가장 큰 우려 중 하나는 효율성입니다. Lambda 표현식은 클래스 (일반적으로 익명 클래스)로 컴파일되며 Java에서 객체 생성은 무거운 작업입니다. 함수를 인라인으로 만들어 모든 이점을 유지하면서 고차 함수를 효과적으로 사용할 수 있습니다.

여기에 인라인 함수 가 그림으로 나옵니다.

함수가로 표시되면 inline코드 컴파일 중에 컴파일러가 모든 함수 호출을 함수의 실제 본문으로 바꿉니다. 또한 인수로 제공된 람다 식은 실제 본문으로 대체됩니다. 함수로 취급되지 않고 실제 코드로 취급됩니다.

간단히 말해서 :- 인라인-> 호출되는 대신 컴파일 타임에 함수의 본문 코드로 대체됩니다.

Kotlin에서는 함수를 다른 함수 (소위 고차 함수라고 함)의 매개 변수로 사용하는 것이 자바에서보다 더 자연스럽게 느껴집니다.

하지만 람다를 사용하면 몇 가지 단점이 있습니다. 익명 클래스 (따라서 객체)이기 때문에 메모리가 필요합니다 (앱의 전체 메서드 수에 추가 될 수도 있음). 이를 방지하기 위해 메서드를 인라인 할 수 있습니다.

fun notInlined(getString: () -> String?) = println(getString())

inline fun inlined(getString: () -> String?) = println(getString())

위의 예에서 :-이 두 함수는 정확히 같은 일을합니다-getString 함수의 결과를 출력합니다. 하나는 인라인되고 하나는 그렇지 않습니다.

디 컴파일 된 자바 코드를 확인하면 메서드가 완전히 동일하다는 것을 알 수 있습니다. 인라인 키워드는 코드를 호출 사이트에 복사하라는 컴파일러에 대한 명령이기 때문입니다.

그러나 아래와 같이 다른 함수에 함수 유형을 전달하는 경우 :

//Compile time error… Illegal usage of inline function type ftOne...
 inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
 }

이를 해결하기 위해 다음과 같이 함수를 다시 작성할 수 있습니다.

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

아래와 같은 고차 함수가 있다고 가정합니다.

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

여기서 컴파일러는 람다 매개 변수가 하나만 있고 다른 함수에 전달하는 경우 인라인 키워드를 사용하지 않도록 지시합니다. 따라서 위의 함수를 아래와 같이 다시 작성할 수 있습니다.

fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
}

참고 :-인라인 함수에만 사용할 수 있으므로 noinline 키워드도 제거해야했습니다!

다음과 같은 기능이 있다고 가정합니다 .-- >

fun intercept() {
    // ...
    val start = SystemClock.elapsedRealtime()
    val result = doSomethingWeWantToMeasure()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    // ...}

이것은 잘 작동하지만 함수 로직의 핵심이 측정 코드로 오염되어 동료가 진행중인 작업을 더 어렵게 만듭니다. :)

인라인 함수가이 코드를 도울 수있는 방법은 다음과 같습니다.

      fun intercept() {
    // ...
    val result = measure { doSomethingWeWantToMeasure() }
    // ...
    }

     inline fun <T> measure(action: () -> T) {
    val start = SystemClock.elapsedRealtime()
    val result = action()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    return result
    }

이제 측정 코드 줄을 건너 뛰지 않고 intercept () 함수의 주요 의도를 읽는 데 집중할 수 있습니다. 또한 우리가 원하는 다른 장소에서 해당 코드를 재사용 할 수있는 옵션의 혜택을받습니다.

인라인을 사용하면 measure (myLamda)와 같은 람다를 전달하는 대신 클로저 ({...}) 내에서 람다 인수를 사용하여 함수를 호출 할 수 있습니다.


2

하나를 원할 수있는 간단한 경우 중 하나는 일시 중단 블록을받는 util 함수를 만들 때입니다. 이걸 고려하세요.

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

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

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

이 경우 타이머는 일시 중지 기능을 허용하지 않습니다. 그것을 해결하기 위해, 당신은 그것을 일시 중단하도록 유혹 할 수도 있습니다.

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

그러나 코 루틴 / 일시 중지 함수 자체에서만 사용할 수 있습니다. 그런 다음 이러한 유틸리티의 비동기 버전과 비 비동기 버전을 만들게됩니다. 인라인으로 만들면 문제가 사라집니다.

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

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

    launch {
        timer { asyncLogic() }
    }
}

다음은 오류 상태 가있는 kotlin 놀이터 입니다. 그것을 해결하기 위해 타이머를 인라인으로 만드십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.