코틀린에서 고차함수(High order functions, 함수를 인자로 전달하거나 함수를 리턴)를 사용하면 추가적인 메모리 할당 및 함수호출로 Runtime overhead가 발생합니다. inline functions는 내부적으로 함수 내용을 호출되는 위치에 복사하며, Runtime overhead를 줄여줍니다.
고차함수의 Runtime penalties
먼저 kotlin에서 고차함수(Higher order functions, 인자가 함수이거나 함수를 리턴하는 함수)를 사용할 때 어떤 Runtime overhead가 있는지 알아보겠습니다.
kotlin은 다음과 같이 함수를 인자로 전달하는 Lamda expression을 정의할 수 있습니다.
fun someMethod(a: Int, func: () -> Unit):Int {
func()
return 2*a
}
fun main(args: Array<String>) {
var result = someMethod(2, {println("Just some dummy function")})
println(result)
}
위의 코드를 다음과 같이 자바로 변환해보면, someMethod
메소드를 호출하기 위해 객체를 생성합니다.
public final class InlineFunctions {
public static final InlineFunctions INSTANCE;
public final int someMethod(int a, @NotNull Function0 func) {
func.invoke();
return 2 * a;
}
@JvmStatic
public static final void main(@NotNull String[] args) {
int result = INSTANCE.someMethod(2, (Function0)null.INSTANCE);
System.out.println(result);
}
static {
InlineFunctions var0 = new InlineFunctions();
INSTANCE = var0;
}
}
내부적으로 객체 생성과 함수 호출을 하도록 구현이 되어있으며, 이런 부분이 성능을 떨어뜨릴 수 있습니다.
inline functions 구현 및 동작 원리
다음처럼 함수 앞에 inline 키워드를 붙이면 inline function이 됩니다.
inline fun someMethod(a: Int, func: () -> Unit):Int {
func()
return 2*a
}
위의 코드가 컴파일될 때, 컴파일러는 함수 내부의 코드를 호출하는 위치에 복사합니다. 컴파일되는 바이트코드의 양은 많아지겠지만, 함수 호출을 하거나 추가적인 객체를 생성하는 부분은 없습니다.
위의 코드를 자바로 컴파일해보면, 다음과 같이 코드가 복사된 것을 알 수 있습니다.
public final class InlineFunctions {
@JvmStatic
public static final void main(@NotNull String[] args) {
int a = 2;
int var5 = false;
String var6 = "Just some dummy function";
System.out.println(var6);
int result = 2 * a;
System.out.println(result);
}
}
이런 이유로 inline functions는 일반 함수보다 성능이 좋습니다. 하지만, inline functions는 내부적으로 코드를 복사하기 때문에, 인자로 전달받은 함수는 다른 함수로 전달되거나 참조될 수 없습니다.
아래 코드에서 newMethod()
는 inline으로 선언되었으며, someMethod()
를 호출하며 함수를 인자로 전달합니다.
이 코드를 컴파일해보면 someMethod(10, func2)
때문에 컴파일이 실패합니다.
inline 함수에서 인자로 전달받은 함수는 참조할 수 없기 때문에 전달하는 것이 불가능합니다.
inline fun newMethod(a: Int, func: () -> Unit, func2: () -> Unit) {
func()
someMethod(10, func2)
}
fun someMethod(a: Int, func: () -> Unit):Int {
func()
return 2*a
}
fun main(args: Array<String>) {
newMethod(2, {println("Just some dummy function")},
{println("can't pass function in inline functions")})
}
다음과 같은 컴파일 에러가 발생합니다.
Error:(9, 24) Kotlin: Illegal usage of inline-parameter 'func2' in 'public final inline fun newMethod(a: Int, func: () -> Unit, func2: () -> Unit): Unit defined in example.InlineFunctions'. Add 'noinline' modifier to the parameter declaration
noinline keyword
위의 예제처럼 모든 인자를 inline으로 처리하고 싶지 않을 때가 있습니다.
인자 앞에 noinline 키워드를 사용하면 그 인자는 inline에서 제외됩니다. 즉, 인자를 다른 함수의 인자로 전달할 수 있습니다.
다음은 위의 코드에서 newMethod()
의 인자 func2
앞에 noinline
키워드를 붙였습니다.
컴파일은 성공하며, 이 키워드가 붙은 함수만 제외하고 모두 inline으로 처리됩니다.
inline fun newMethod(a: Int, func: () -> Unit, noinline func2: () -> Unit) {
func()
someMethod(10, func2)
}
fun someMethod(a: Int, func: () -> Unit):Int {
func()
return 2*a
}
@JvmStatic
fun main(args: Array<String>) {
newMethod(2, {println("Just some dummy function")},
{println("can't pass function in inline functions")})
}
다음은 위의 코드를 자바로 변환한 것이고, func2()
를 제외한 나머지 코드들은 모두 inline으로 처리되었습니다.
public final void newMethod(int a, @NotNull Function0 func, @NotNull Function0 func2) {
func.invoke();
this.someMethod(10, func2);
}
public final int someMethod(int a, @NotNull Function0 func) {
func.invoke();
return 2 * a;
}
@JvmStatic
public static final void main(@NotNull String[] args) {
String var6 = "Just some dummy function";
System.out.println(var6);
this_$iv.someMethod(10, func2$iv);
}
정리
inline 키워드를 이용하면 런타임 오버헤드를 줄일 수 있다는 것을 알았습니다. 하지만 코드 양이 많은 함수를 inline하면 bytecode 양이 많아질 수 있고 최적화되지 않을 수 있습니다. inline keyword는 1~3줄 정도 길이의 함수에 사용하는 것이 효과적일 수 있습니다.
참고
Related Posts
- Kotlin - 배열에서 최소 값, 최대 값 찾기
- Kotlin - 2차원 배열 선언, 초기화 방법
- Kotlin - 배열 선언, 초기화 방법
- Kotlin - 리스트, 배열 길이 가져오기
- Kotlin - 리스트에서 최대, 최소 값 찾기
- Kotlin - for 반복문, 배열/리스트 순회
- Kotlin - Timer, 주기적으로 함수 실행
- Kotlin - sleep, 쓰레드 몇 초 지연
- Kotlin - Thread 생성 및 실행
- Kotlin에서 정규표현식 사용하기
- Kotlin - 문자열 길이 계산
- Kotlin - 문자열 비교 방법(equals, ==, compareTo)
- Kotlin - 2개의 배열 하나로 합치기
- Kotlin - 2개의 List 하나로 합치기
- Kotlin - 디렉토리의 모든 파일 리스트 출력
- Kotlin - 리스트 정렬 방법 (sort, sortBy, sortWith)
- Kotlin - 문자열 뒤집기 (Reverse String)
- Kotlin - 랜덤 숫자 생성 (Random, SecureRandom)
- Kotlin - Range, 숫자 범위 표현
- Kotlin - 음수를 양수로 변환, math.abs()
- Kotlin - List를 Set로 변환
- Kotlin - Set를 List로 변환
- Kotlin - 문자열에서 숫자(int)만 추출하는 방법
- Kotlin - Map을 List로 변환하는 방법
- Kotlin - File, Directory가 존재하는지 확인
- Kotlin - List를 Map으로 변환
- Kotlin - List의 중복 요소 제거
- Kotlin - List를 Array로 변환
- Kotlin - 엘비스 연산자 (Elvis Operation)
- Kotlin - Array를 List로 변환
- Kotlin - String을 Float으로 변환
- Kotlin - String을 Double으로 변환
- Kotlin - String을 Int로 변환
- Kotlin - String을 Long으로 변환
- Kotlin - String Null 또는 Empty 체크