Skip to content

Commit c5caccc

Browse files
committed
inline functions
1 parent 7170201 commit c5caccc

File tree

15 files changed

+411
-0
lines changed

15 files changed

+411
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
type: edu
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import kotlin.random.Random
2+
3+
fun main() {
4+
5+
val counter = createCounter { Random.nextInt(10) }
6+
7+
println(counter())
8+
println(counter())
9+
println(counter())
10+
}
11+
12+
inline fun createCounter(numberCreator: () -> Int): () -> Int {
13+
var count = numberCreator()
14+
return {
15+
count++
16+
}
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type: choice
2+
is_multiple_choice: true
3+
options:
4+
- text: A developer can achieve optimization using the inline keyword as a silver bullet.
5+
is_correct: false
6+
- text: Closure captures the references to variables of the enclosing scope of a lambda.
7+
is_correct: true
8+
- text: Closure captures the value to variables of the enclosing scope of a lambda.
9+
is_correct: false
10+
- text: Closure is a concept with great benefits only.
11+
is_correct: false
12+
- text: Using the inline keyword thoughtlessly may introduce negative effects.
13+
is_correct: true
14+
files:
15+
- name: src/Main.kt
16+
visible: true
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Closure
2+
3+
Lambdas can capture and have access to their surrounding scope, the closure.
4+
The closure needs to maintain the state of the variables.
5+
This is true even after the function has finished executing.
6+
7+
Hence, closures introduce a runtime overhead because additional references must be maintained leading to a memory overhead.
8+
9+
## Facts
10+
11+
### Closure Creation
12+
13+
When a higher-order function creates a closure, it captures references to the variables from its enclosing scope.
14+
This involves storing these references within the closure object itself.
15+
16+
### Closure Lifetime
17+
18+
The closure is still alive even after the enclosing function has completed execution.
19+
The closure may still hold references to variables from the enclosing scope.
20+
21+
### Garbage Collection
22+
23+
Closure retains references to variables from the enclosing scope.
24+
Those variables cannot be garbage-collected as long as the closure exists.
25+
26+
### Additional Objects
27+
28+
The closure itself may introduce additional objects or overhead.
29+
Like
30+
* compiler-generated class to hold the captured variables
31+
* additional metadata needed to support closures in the runtime environment.
32+
33+
## Decompiled Example
34+
35+
```java
36+
public static final void main() {
37+
Function0 counter = createCounter((Function0)null.INSTANCE);
38+
int var1 = ((Number)counter.invoke()).intValue();
39+
System.out.println(var1);
40+
var1 = ((Number)counter.invoke()).intValue();
41+
System.out.println(var1);
42+
var1 = ((Number)counter.invoke()).intValue();
43+
System.out.println(var1);
44+
}
45+
46+
@NotNull
47+
public static final Function0 createCounter(@NotNull Function0 numberCreator) {
48+
Intrinsics.checkNotNullParameter(numberCreator, "numberCreator");
49+
final Ref.IntRef count = new Ref.IntRef();
50+
count.element = ((Number)numberCreator.invoke()).intValue();
51+
return (Function0)(new Function0() {
52+
// $FF: synthetic method
53+
// $FF: bridge method
54+
public Object invoke() {
55+
return this.invoke();
56+
}
57+
58+
public final int invoke() {
59+
Ref.IntRef var10000 = count;
60+
int var1;
61+
var10000.element = (var1 = var10000.element) + 1;
62+
return var1;
63+
}
64+
});
65+
}
66+
```
67+
68+
## Decompiled with inline keyword
69+
70+
```java
71+
public final class MainKt {
72+
public static final void main() {
73+
int $i$f$createCounter = false;
74+
Ref.IntRef count$iv = new Ref.IntRef();
75+
int var3 = false;
76+
int var5 = Random.Default.nextInt(10);
77+
count$iv.element = var5;
78+
Function0 counter = (Function0) (new Function0() {
79+
// $FF: synthetic method
80+
// $FF: bridge method
81+
public Object invoke() {
82+
return this.invoke();
83+
}
84+
85+
public final int invoke() {
86+
Ref.IntRef var10000 = count;
87+
int var1;
88+
var10000.element = (var1 = var10000.element) + 1;
89+
return var1;
90+
}
91+
});
92+
int var6 = ((Number) counter.invoke()).intValue();
93+
System.out.println(var6);
94+
var6 = ((Number) counter.invoke()).intValue();
95+
System.out.println(var6);
96+
var6 = ((Number) counter.invoke()).intValue();
97+
System.out.println(var6);
98+
}
99+
100+
@NotNull
101+
public static final Function0 createCounter(@NotNull Function0 numberCreator) {
102+
int $i$f$createCounter = 0;
103+
Intrinsics.checkNotNullParameter(numberCreator, "numberCreator");
104+
final Ref.IntRef count = new Ref.IntRef();
105+
count.element = ((Number)numberCreator.invoke()).intValue();
106+
return (Function0)(new Function0() {
107+
// $FF: synthetic method
108+
// $FF: bridge method
109+
public Object invoke() {
110+
return this.invoke();
111+
}
112+
113+
public final int invoke() {
114+
Ref.IntRef var10000 = count;
115+
int var1;
116+
var10000.element = (var1 = var10000.element) + 1;
117+
return var1;
118+
}
119+
});
120+
}
121+
}
122+
```
123+
124+
## Observation
125+
126+
The `inline` keyword doesn't offer any significant benefits.
127+
Inlining a function that returns a lambda with captured variables doesn't get inlined in the call site.
128+
The closure capturing still happens.
129+
130+
The decompiled code has an overhead.
131+
The lambda function to return is compiled twice.
132+
* In the main method as the `counter` function due to the inlining of the `createCounter` higher-order function.
133+
* As separate function `createCounter` which is not used
134+
135+
## Conclusion
136+
137+
The `inline` keyword is no silver bullet for performance optimizations.
138+
Used in the wrong context it will not gain any signification optimizations.
139+
Even worse it will create some code bloat introducing a negative effect.
140+
141+
> It is essential to use keyword with care in the right context to gain benefits.
142+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fun main() {
2+
highOrderFunction {
3+
println(it)
4+
}
5+
}
6+
7+
fun highOrderFunction(lambda: (String) -> Unit) {
8+
lambda("Hello World")
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
type: choice
2+
is_multiple_choice: true
3+
options:
4+
- text: Kotlins functions are first-class citizens which can take other functions as parameters.
5+
is_correct: true
6+
- text: These functions are called higher-order functions.
7+
is_correct: true
8+
- text: The call site is place where the higher-order function is invoked.
9+
is_correct: true
10+
- text: The inline keyword has an impact on the compilation of high-order functions.
11+
is_correct: true
12+
files:
13+
- name: src/Main.kt
14+
visible: true
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Inline Functions
2+
3+
Kotlin offers first class functions.
4+
These let you store functions in variables, pass them as arguments to and return them from other higher-order functions.
5+
6+
As each function is an object, creating higher-order functions leads to a new object creation and memory allocation.
7+
8+
Inline Functions are a powerful feature to improve performance and reduce overhead of higher-order functions.
9+
Marking a function with the `inline` keyword, the compiler replaces every call to that function with the actual function body.
10+
Hence, the function body is inlined at the call site and object creation is avoided.
11+
12+
## Benefits
13+
14+
### Function Body Replacement
15+
16+
Using `inline` on a function the compiler replaces every call to that function with the actual function body of the lambda.
17+
This avoids the overhead of a function call.
18+
19+
### Context Preservation
20+
21+
Inline functions can access variables from the surrounding scope including private variables.
22+
This possible due to the fact that the code is essentially copied into the call site, so it has access to all the variables available at that location.
23+
24+
### Improves Performance
25+
26+
By eliminating the overhead of function calls, inline functions can improve the performance of your code.
27+
Especially in scenarios calling small functions frequently within loops.
28+
29+
## Example
30+
31+
### Simple Function
32+
```kotlin
33+
// Declaration
34+
fun highOrderFunction(lambda: (String) -> Unit) {
35+
lambda("Hello World")
36+
}
37+
38+
// Call
39+
highOrderFunction {
40+
println(it)
41+
}
42+
```
43+
44+
#### Decompiled
45+
46+
> For sake of simplicity the meta information are omitted
47+
48+
```java
49+
public final class MainKt {
50+
public static final void main() {
51+
highOrderFunction((Function1)null.INSTANCE);
52+
}
53+
54+
// $FF: synthetic method
55+
public static void main(String[] var0) {
56+
main();
57+
}
58+
59+
public static final void highOrderFunction(@NotNull Function1 lambda) {
60+
Intrinsics.checkNotNullParameter(lambda, "lambda");
61+
lambda.invoke("Hello World");
62+
}
63+
}
64+
```
65+
66+
* There is an instantiation of the lambda -> Function1
67+
* There is a call to `invoke` to trigger the lambda
68+
69+
### As Inline Function
70+
71+
```kotlin
72+
// Declaration
73+
inline fun highOrderFunction(lambda: (String) -> Unit) {
74+
lambda("Hello World")
75+
}
76+
77+
// Call
78+
highOrderFunction {
79+
println(it)
80+
}
81+
```
82+
83+
#### Decompiled
84+
85+
> For sake of simplicity the meta information are omitted
86+
87+
```java
88+
public final class MainKt {
89+
public static final void main() {
90+
int $i$f$highOrderFunction = false;
91+
String it = "Hello World";
92+
int var2 = false;
93+
System.out.println(it);
94+
}
95+
96+
// $FF: synthetic method
97+
public static void main(String[] var0) {
98+
main();
99+
}
100+
101+
public static final void highOrderFunction(@NotNull Function1 lambda) {
102+
int $i$f$highOrderFunction = 0;
103+
Intrinsics.checkNotNullParameter(lambda, "lambda");
104+
lambda.invoke("Hello World");
105+
}
106+
}
107+
```
108+
109+
* The `higherOrderFunction` is compiled but not called
110+
* The lambda passed to the function is inlined in the `main()`
111+
* The `println` statement is inlined
112+
* No instantiation of the higher-order function
113+
* No invocation of the lambda
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Wont compile
2+
3+
//fun main() {
4+
// val main = Main()
5+
//
6+
// main.publicInlineFun { main.privateFun() }
7+
//}
8+
//
9+
//class Main {
10+
//
11+
// private fun privateFun(): Int {
12+
// return 42
13+
// }
14+
//
15+
// inline fun publicInlineFun(lambda: () -> Unit) {
16+
// lambda()
17+
// privateFun()
18+
// }
19+
//}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
type: choice
2+
is_multiple_choice: true
3+
options:
4+
- text: Inline functions have access to all members at any time.
5+
is_correct: false
6+
- text: The @PublishedApi annotations purpose is to control the member visibility of classes.
7+
is_correct: false
8+
- text: Inline functions can be private.
9+
is_correct: true
10+
files:
11+
- name: src/Main.kt
12+
visible: true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Visibility
2+
3+
Inline functions are restricted to be public.
4+
This is because of how inlining works and the complications it introduces to the visibility of the functions code on the call site.
5+
6+
The body of an inline function gets copied directly at the call site during compilation.
7+
If the function were marked as `private`, then the inlined code would only be accessible within the same file where the function is defined.
8+
9+
However, inlining effectively breaks this file-level encapsulation because the inlined code is copied into other files where the function is called.
10+
This would mean that the inlined code could potentially be accessible outside the file where the private function is defined, violating encapsulation.
11+
12+
To prevent this from happening and to maintain the encapsulation provided by private visibility, Kotlin does not allow private functions to be marked as inline.
13+
Instead, if you need to inline a function for performance reasons, you can consider using internal visibility, which allows the function to be accessed from within the same module but not outside of it.
14+
15+
## @PublishedApi
16+
17+
The `@PublishedApi` annotation is used to mark declarations that are intended to be part of the public API of a module, but are not meant to be exposed to consumers of that module.
18+
This annotation is often used in conjunction with inline functions to control the visibility.
19+
Marked functions are used internally within the module but should not be exposed externally.

0 commit comments

Comments
 (0)