— kotlin, programming — 2 min read
inline
키워드를 사용하는 것이 이점을 준다kotlin stdlib 의 모든 고차원 함수(higher-order function) 들은 inline
modifier을 가지고 있다
cf.) 고차원 함수 : 함수를 인자로 받거나 리턴값으로 사용할 수 있는 함수
example
1inline fun repeat(times: Int, action: (Int) -> Unit) {2 for (index in 0 until times) {3 action(index)4 }5}
일반적인 함수는 해당 함수가 불려졌을때 그 함수의 body로 점프하게 됩니다. 해당 함수의 실행이 끝나게 되면 호출되었던 곳으로 다시 돌아가게 됩니다.
반면, inline function의 경우에는 compile 시에 해당 함수를 호출한 곳을 해당 함수의 body로 대체되게 됩니다.
ex)
1// before compilation2repeat(10) {3 println(it)4}5// after compilation6for (index in 0 until 10) {7 println(index)8}
body를 대체함으로 얻을 수 있는 이점
<T>
) 가 구체화될수 있다기본적으로 generic 타입은 컴파일시에 지워지게 됩니다.
List<Int>
-> List
따라서, 객체의 타입이 List<Int>
인지에 대해서 확인이 불가하게 됩니다.
1any is List<Int> // complie error2any is List<*> // ok3
4fun <T> printTypeName() {5 print(T::class.simpleName) // error6}
하지만 이것은 inline 함수의 경우에는 가능하게 됩니다.
reified
modifier와 함께 사용시 type argument로 사용됩니다1inline fun <reified T> printTypeName() {2 print(T::class.simpleName)3}4
5// usage6printTypeName<Int>()7printTypeName<Char>()8printTypeName<String>()9
10// after compilation11print(Int::class.simpleName)12print(Char::class.simpleName)13print(String::class.simpleName)
인라인 함수의 경우 모든 경우 non inline 함수에 비해 약간씩 빠릅니다. (함수 호출시 함수 본문으로 건너뛰고 다시 돌아오는 과정이 없기 때문에)
이로 인해 kotlin stdlib의 짧은 함수들의 종종 inline 함수로 구현되어있는 이유입니다.
하지만, functional parameter를 가지고 있지 않은 함수라면 유의미한 차이를 가지지 않습니다.
Intellij 에서는 functional parameter 를 가지고 있지 않은 함수를 인라인 하려고 하는 경우 warning을 띄워줍니다
예를 들어
1val lambda: ()->Unit = {2 // code3}
위와 같은 람다 표현식은 다음과 같이 컴파일 되게 됩니다.
1Function0<Unit> lambda = new Function0<Unit>() {2 public Unit invoke() {3 // code4 }5};6
7// or8public class Test$lambda implements Function0<Unit> {9 public Unit invode() {10 // code11 }12}13Function0 lambda = new Test$lambda();
lambda 표현식의 컴파일 방식
()->Unit
-> Function0<Unit>
()->Int
-> Function0<Int>
(Int)->Int
-> Function1<Int,Int>
(Int, Int)->Int
-> Function2<Int,Int,Int>
따라서 이러한 컴파일 과정이 추가적으로 필요하기 때문에 inline이 아닐경우 더 오래걸리게 됩니다.
또한 로컬 변수를 람다 내에서 가지고 있을 경우에는 inline
과 non-inline
과의 차이가 더 커지게 됩니다. non-inline 일 경우 변수의 reference를 넘겨주어야 하기 때문에...
1var l = 1L2noinlineRepeat(100_000_000) {3 l += it4}5// compile6val a = Ref.LongRef()7a.element = 1L8noinlineRepeat(100_000_000) {9 a.element = a.element + it10}
실행 시간의 비교
1@Benchmark2// on average 189ms3fun nothingInline(blackhole: Blackhole) {4 repeat(100_000_000) {5 blackhole.consume(it)6 }7}8
9@Benchmark10// on average 447ms11fun nothingNonInline(blackhole: Blackhole) {12 noinlineRepeat(100_000_000) {13 blackhole.consume(it)14 }15}16
17
18// when capture local variable19
20@Benchmark21// on average 30ms22fun nothingInline(blackhole: Blackhole) {23 var l = 0L24 repeat(100_000_000) {25 l += it26 }27 blackhole.consume(it)28}29
30@Benchmark31// on average 274ms32fun nothingNonInline(blackhole: Blackhole) {33 var l = 0L34 noinlineRepeat(100_000_000) {35 l += it36 }37 blackhole.consume(it)38}
기본적으로 코틀린은 람다 내부에서 return을 허용하지 않습니다.
function literal 들은 계속 이야기 하였듯이 컴파일이 되기 때문에 람다내의 코드는 다른 클래스에 위치하게 됩니다. 따라서, main 을 리턴시킬수 없습니다. 이러한 제한은 inline
되는 것으로 해결할 수 있습니다.
1fun getSomeMoney(): Money? {2 repeatNoinline(10) {3 val money = searchForMoney(it)4 if(money != null) return money // ERROR5 }6 return null7}8fun getSomeMoney(): Money? {9 repeat(10) {10 val money = searchForMoney(it)11 if(money != null) return money // OK12 }13 return null14}
몇몇 경우에서 function 자체는 inline 을 원하고 function type argument는 inline을 원하지 않을때 사용할 수 있는 modifier 들이 있다.
crossinline
noinline
1inline fun requestNewToken(2 hasToken: Boolean,3 crossinline onRefresh: ()->Unit,4 noinline onGenerate: ()->Unit5) {6 if (hasToken) {7 httpCall("get-token", onGenerate)8 } else {9 httpCall("refresh-token") {10 onRefresh()11 onGenerate()12 }13 }14}15
16fun httpCall(url: String, callback: ()->Unit) {17 /*...*/18}
intellij를 사용한다면, 이 두가지 modifier의 정확하게 기억하고 있을 필요는 없다. intellij 가 추천해준다. 다만 이러한 것이 있다는 것 정도만 기억하고 있으면 좋다
1. inline 함수를 재귀적으로 사용해서는 안된다
1inline fun a() { b() }2inline fun b() { c() }3inline fun c() { a() }
해당 호출은 무한적으로 호출이 되고, 코드 작성시에 어떠한 컴파일 에러도 보여주지 않기 때문에 문제를 초래할 수 있다.
2. inline 함수는 더 제한적인 접근 제한자를 가진 element를 사용할수 없다
1inline fun read() {2 val reader = Reader() // ERROR3 // ...4}5
6private class Reaer {7 // ...8}
3. 코드의 양을 비대하게 만들수 있다.
과도한 inline
의 남발은 코드의 양을 굉장히 크게 만들 수 있습니다.
1inline fun printThree() {2 print(3)3}4inline fun threePrintThree() {5 printThree()6 printThree()7 printThree()8}9inline fun threeThreePrintThree() {10 threePrintThree()11 threePrintThree()12 threePrintThree()13}14inline fun threeThreePrintThree() {15 threeThreePrintThree()16 threeThreePrintThree()17 threeThreePrintThree()18}19// after compilation20inline fun printThree() {21 print(3)22}23inline fun threePrintThree() {24 print(3)25 print(3)26 print(3)27}28inline fun threeThreePrintThree() {29 print(3) // call print(3) * 9 times30 print(3)31 ...32 print(3)33}34inline fun threeThreePrintThree() {35 print(3) // call print(3) * 27 times36 print(3)37 ...38 print(3)39}