Tìm hiểu về inline function trong kotlin

I, Inline function

1, Vấn đề

  • Trước khi đọc bài viết này, mình khuyên bạn nên đọc về high-order function trước.
  • High-order function gặp vấn đề là mỗi parameter có function type cần được khởi tạo bằng 1 function object trong memory và tạo ra runtime overhead.
  • Ví dụ 1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class InlineExample {
// Test with anonymous function
fun testAnonymous() {
makeHighOrder(fun() = 1)
}

// Test with lambada function
fun testLambada() {
makeHighOrder { 1 }
}

private fun makeHighOrder(action: () -> Int) {
action()
}
}
  • Khi chúng ta dùng xem nó được compile qua java code bằng Tools > Kotlin > Show kotlin bytecode > Decompile, chúng ta sẽ thấy class InlineExample.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public final class InlineExample {
public final void testAnonymous() {
this.makeHighOrder((Function0)null.INSTANCE);
}

public final void testLambada() {
this.makeHighOrder((Function0)null.INSTANCE);
}

private final void makeHighOrder(Function0 action) {
action.invoke();
}
}
  • Ta có thể thấy makeHighOrder() trong InlineExample.java có 1 tham số action có kiểu Function0.
  • Khi nó được sử dụng ở testAnonymous()testLambada() thì sẽ tạo ra 2 object Function0.
  • Trong kotlin, chúng ta không tạo ra đối tượng nào cả ? Đây cũng là vấn đề của high-order function.

2, Inline function

  • Ví dụ 2: tương tự như ví dụ 1 nhưng mình sẽ thêm inline keyword vào trước makeHighOrder()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class InlineExample {
// Test with anonymous function
fun testAnonymous() {
makeHighOrder(fun() = 1)
}

// Test with lambada function
fun testLambada() {
makeHighOrder { 1 }
}

// Add `inline` keyword
private inline fun makeHighOrder(action: () -> Int) {
action()
}
}
  • Khi đó, InlineExample sẽ được compile sang java code như sau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class InlineExample {
public final void testAnonymous() {
int $i$f$makeHighOrder = false;
boolean var3 = false;
}

public final void testLambada() {
int $i$f$makeHighOrder = false;
boolean var3 = false;
}

private final void makeHighOrder(Function0 action) {
int $i$f$makeHighOrder = 0;
action.invoke();
}
}
  • Mặc dù makeHighOrder() vẫn được compile gần giống như cũ.
  • Nhưng khi gọi testAnonymous()testLambada() nhưng không tạo ra Function0 nào nữa.
  • Có chuyện gì xảy ra khi ta sử dụng inline function ?
  • Từ khóa inline đã copy implement của function vào nơi gọi function (call-site) đó. Do đó các call-site không tạo ra bất cứ object nào.
  • Chú ý:
    • Inline function được dùng để tối ưu cho high-order function.
    • Nếu bạn thêm inline vào những function thông thường sẽ không có ý nghĩa về mặt tối ưu. Android studio sẽ có warning và suggest bạn bỏ inline đi.
1
2
// No performance impact. We will get warning here
inline fun test(a: Int){}

3, Giới hạn và cách dùng inline function

  • Nhược điểm của inline function mà chúng ta có thể thấy ngay là chúng có thể gây ra nhiều code thừa vì implement của nó có thể dùng ở nhiều nơi.
  • Inline function dùng ở đâu ?
  • Chúng ta nên tránh sử dụng inline cho những function lớn. Chúng ta nên inline cho những function nhỏ (vài ba dòng).
  • Nếu bạn xem source code của file _Collections.kt thì bạn sẽ thấy các inline function chỉ có từ 1 đến 3 dòng.

II, Noinline

  • Inline function không cho phép parameter có function type:
    • Gán cho 1 local variable.
    • Truyền vào trong function khác ở body.
  • Ví dụ 3:
1
2
3
4
5
6
7
8
9
10
11
12
private inline fun makeHighOrder(
action: () -> Int,
notInline: () -> Unit
) {
action()
val t = notInline // Compilation error
makeNotInline(notInline) // Compilation error
}

fun makeNotInline(notInline: () -> Unit){

}
  • Trong trường hợp này, chúng ta có thể đánh dấu notInline bằng từ khoá noinline như ví dụ 4.
  • Ví dụ 4: sửa error ở ví dụ 3
1
2
3
4
5
6
7
8
9
10
11
12
private inline fun makeHighOrder(
action: () -> Int,
noinline notInline: () -> Unit
) {
action()
val t = notInline // It is ok because of `noinline`
makeNotInline(notInline) // It is ok because of `noinline`
}

fun makeNotInline(notInline: () -> Unit) {

}
  • Khi sử dụng noinline cho biến notInline thì khi khởi tạo notInline lại tạo ra object trong bộ nhớ.