-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andreas Haindl
committed
Mar 19, 2024
1 parent
7170201
commit c5caccc
Showing
15 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
type: edu |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import kotlin.random.Random | ||
|
||
fun main() { | ||
|
||
val counter = createCounter { Random.nextInt(10) } | ||
|
||
println(counter()) | ||
println(counter()) | ||
println(counter()) | ||
} | ||
|
||
inline fun createCounter(numberCreator: () -> Int): () -> Int { | ||
var count = numberCreator() | ||
return { | ||
count++ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
type: choice | ||
is_multiple_choice: true | ||
options: | ||
- text: A developer can achieve optimization using the inline keyword as a silver bullet. | ||
is_correct: false | ||
- text: Closure captures the references to variables of the enclosing scope of a lambda. | ||
is_correct: true | ||
- text: Closure captures the value to variables of the enclosing scope of a lambda. | ||
is_correct: false | ||
- text: Closure is a concept with great benefits only. | ||
is_correct: false | ||
- text: Using the inline keyword thoughtlessly may introduce negative effects. | ||
is_correct: true | ||
files: | ||
- name: src/Main.kt | ||
visible: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# Closure | ||
|
||
Lambdas can capture and have access to their surrounding scope, the closure. | ||
The closure needs to maintain the state of the variables. | ||
This is true even after the function has finished executing. | ||
|
||
Hence, closures introduce a runtime overhead because additional references must be maintained leading to a memory overhead. | ||
|
||
## Facts | ||
|
||
### Closure Creation | ||
|
||
When a higher-order function creates a closure, it captures references to the variables from its enclosing scope. | ||
This involves storing these references within the closure object itself. | ||
|
||
### Closure Lifetime | ||
|
||
The closure is still alive even after the enclosing function has completed execution. | ||
The closure may still hold references to variables from the enclosing scope. | ||
|
||
### Garbage Collection | ||
|
||
Closure retains references to variables from the enclosing scope. | ||
Those variables cannot be garbage-collected as long as the closure exists. | ||
|
||
### Additional Objects | ||
|
||
The closure itself may introduce additional objects or overhead. | ||
Like | ||
* compiler-generated class to hold the captured variables | ||
* additional metadata needed to support closures in the runtime environment. | ||
|
||
## Decompiled Example | ||
|
||
```java | ||
public static final void main() { | ||
Function0 counter = createCounter((Function0)null.INSTANCE); | ||
int var1 = ((Number)counter.invoke()).intValue(); | ||
System.out.println(var1); | ||
var1 = ((Number)counter.invoke()).intValue(); | ||
System.out.println(var1); | ||
var1 = ((Number)counter.invoke()).intValue(); | ||
System.out.println(var1); | ||
} | ||
|
||
@NotNull | ||
public static final Function0 createCounter(@NotNull Function0 numberCreator) { | ||
Intrinsics.checkNotNullParameter(numberCreator, "numberCreator"); | ||
final Ref.IntRef count = new Ref.IntRef(); | ||
count.element = ((Number)numberCreator.invoke()).intValue(); | ||
return (Function0)(new Function0() { | ||
// $FF: synthetic method | ||
// $FF: bridge method | ||
public Object invoke() { | ||
return this.invoke(); | ||
} | ||
|
||
public final int invoke() { | ||
Ref.IntRef var10000 = count; | ||
int var1; | ||
var10000.element = (var1 = var10000.element) + 1; | ||
return var1; | ||
} | ||
}); | ||
} | ||
``` | ||
|
||
## Decompiled with inline keyword | ||
|
||
```java | ||
public final class MainKt { | ||
public static final void main() { | ||
int $i$f$createCounter = false; | ||
Ref.IntRef count$iv = new Ref.IntRef(); | ||
int var3 = false; | ||
int var5 = Random.Default.nextInt(10); | ||
count$iv.element = var5; | ||
Function0 counter = (Function0) (new Function0() { | ||
// $FF: synthetic method | ||
// $FF: bridge method | ||
public Object invoke() { | ||
return this.invoke(); | ||
} | ||
|
||
public final int invoke() { | ||
Ref.IntRef var10000 = count; | ||
int var1; | ||
var10000.element = (var1 = var10000.element) + 1; | ||
return var1; | ||
} | ||
}); | ||
int var6 = ((Number) counter.invoke()).intValue(); | ||
System.out.println(var6); | ||
var6 = ((Number) counter.invoke()).intValue(); | ||
System.out.println(var6); | ||
var6 = ((Number) counter.invoke()).intValue(); | ||
System.out.println(var6); | ||
} | ||
|
||
@NotNull | ||
public static final Function0 createCounter(@NotNull Function0 numberCreator) { | ||
int $i$f$createCounter = 0; | ||
Intrinsics.checkNotNullParameter(numberCreator, "numberCreator"); | ||
final Ref.IntRef count = new Ref.IntRef(); | ||
count.element = ((Number)numberCreator.invoke()).intValue(); | ||
return (Function0)(new Function0() { | ||
// $FF: synthetic method | ||
// $FF: bridge method | ||
public Object invoke() { | ||
return this.invoke(); | ||
} | ||
|
||
public final int invoke() { | ||
Ref.IntRef var10000 = count; | ||
int var1; | ||
var10000.element = (var1 = var10000.element) + 1; | ||
return var1; | ||
} | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
## Observation | ||
|
||
The `inline` keyword doesn't offer any significant benefits. | ||
Inlining a function that returns a lambda with captured variables doesn't get inlined in the call site. | ||
The closure capturing still happens. | ||
|
||
The decompiled code has an overhead. | ||
The lambda function to return is compiled twice. | ||
* In the main method as the `counter` function due to the inlining of the `createCounter` higher-order function. | ||
* As separate function `createCounter` which is not used | ||
|
||
## Conclusion | ||
|
||
The `inline` keyword is no silver bullet for performance optimizations. | ||
Used in the wrong context it will not gain any signification optimizations. | ||
Even worse it will create some code bloat introducing a negative effect. | ||
|
||
> It is essential to use keyword with care in the right context to gain benefits. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
fun main() { | ||
highOrderFunction { | ||
println(it) | ||
} | ||
} | ||
|
||
fun highOrderFunction(lambda: (String) -> Unit) { | ||
lambda("Hello World") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
type: choice | ||
is_multiple_choice: true | ||
options: | ||
- text: Kotlins functions are first-class citizens which can take other functions as parameters. | ||
is_correct: true | ||
- text: These functions are called higher-order functions. | ||
is_correct: true | ||
- text: The call site is place where the higher-order function is invoked. | ||
is_correct: true | ||
- text: The inline keyword has an impact on the compilation of high-order functions. | ||
is_correct: true | ||
files: | ||
- name: src/Main.kt | ||
visible: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Inline Functions | ||
|
||
Kotlin offers first class functions. | ||
These let you store functions in variables, pass them as arguments to and return them from other higher-order functions. | ||
|
||
As each function is an object, creating higher-order functions leads to a new object creation and memory allocation. | ||
|
||
Inline Functions are a powerful feature to improve performance and reduce overhead of higher-order functions. | ||
Marking a function with the `inline` keyword, the compiler replaces every call to that function with the actual function body. | ||
Hence, the function body is inlined at the call site and object creation is avoided. | ||
|
||
## Benefits | ||
|
||
### Function Body Replacement | ||
|
||
Using `inline` on a function the compiler replaces every call to that function with the actual function body of the lambda. | ||
This avoids the overhead of a function call. | ||
|
||
### Context Preservation | ||
|
||
Inline functions can access variables from the surrounding scope including private variables. | ||
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. | ||
|
||
### Improves Performance | ||
|
||
By eliminating the overhead of function calls, inline functions can improve the performance of your code. | ||
Especially in scenarios calling small functions frequently within loops. | ||
|
||
## Example | ||
|
||
### Simple Function | ||
```kotlin | ||
// Declaration | ||
fun highOrderFunction(lambda: (String) -> Unit) { | ||
lambda("Hello World") | ||
} | ||
|
||
// Call | ||
highOrderFunction { | ||
println(it) | ||
} | ||
``` | ||
|
||
#### Decompiled | ||
|
||
> For sake of simplicity the meta information are omitted | ||
```java | ||
public final class MainKt { | ||
public static final void main() { | ||
highOrderFunction((Function1)null.INSTANCE); | ||
} | ||
|
||
// $FF: synthetic method | ||
public static void main(String[] var0) { | ||
main(); | ||
} | ||
|
||
public static final void highOrderFunction(@NotNull Function1 lambda) { | ||
Intrinsics.checkNotNullParameter(lambda, "lambda"); | ||
lambda.invoke("Hello World"); | ||
} | ||
} | ||
``` | ||
|
||
* There is an instantiation of the lambda -> Function1 | ||
* There is a call to `invoke` to trigger the lambda | ||
|
||
### As Inline Function | ||
|
||
```kotlin | ||
// Declaration | ||
inline fun highOrderFunction(lambda: (String) -> Unit) { | ||
lambda("Hello World") | ||
} | ||
|
||
// Call | ||
highOrderFunction { | ||
println(it) | ||
} | ||
``` | ||
|
||
#### Decompiled | ||
|
||
> For sake of simplicity the meta information are omitted | ||
```java | ||
public final class MainKt { | ||
public static final void main() { | ||
int $i$f$highOrderFunction = false; | ||
String it = "Hello World"; | ||
int var2 = false; | ||
System.out.println(it); | ||
} | ||
|
||
// $FF: synthetic method | ||
public static void main(String[] var0) { | ||
main(); | ||
} | ||
|
||
public static final void highOrderFunction(@NotNull Function1 lambda) { | ||
int $i$f$highOrderFunction = 0; | ||
Intrinsics.checkNotNullParameter(lambda, "lambda"); | ||
lambda.invoke("Hello World"); | ||
} | ||
} | ||
``` | ||
|
||
* The `higherOrderFunction` is compiled but not called | ||
* The lambda passed to the function is inlined in the `main()` | ||
* The `println` statement is inlined | ||
* No instantiation of the higher-order function | ||
* No invocation of the lambda |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Wont compile | ||
|
||
//fun main() { | ||
// val main = Main() | ||
// | ||
// main.publicInlineFun { main.privateFun() } | ||
//} | ||
// | ||
//class Main { | ||
// | ||
// private fun privateFun(): Int { | ||
// return 42 | ||
// } | ||
// | ||
// inline fun publicInlineFun(lambda: () -> Unit) { | ||
// lambda() | ||
// privateFun() | ||
// } | ||
//} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
type: choice | ||
is_multiple_choice: true | ||
options: | ||
- text: Inline functions have access to all members at any time. | ||
is_correct: false | ||
- text: The @PublishedApi annotations purpose is to control the member visibility of classes. | ||
is_correct: false | ||
- text: Inline functions can be private. | ||
is_correct: true | ||
files: | ||
- name: src/Main.kt | ||
visible: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Visibility | ||
|
||
Inline functions are restricted to be public. | ||
This is because of how inlining works and the complications it introduces to the visibility of the functions code on the call site. | ||
|
||
The body of an inline function gets copied directly at the call site during compilation. | ||
If the function were marked as `private`, then the inlined code would only be accessible within the same file where the function is defined. | ||
|
||
However, inlining effectively breaks this file-level encapsulation because the inlined code is copied into other files where the function is called. | ||
This would mean that the inlined code could potentially be accessible outside the file where the private function is defined, violating encapsulation. | ||
|
||
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. | ||
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. | ||
|
||
## @PublishedApi | ||
|
||
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. | ||
This annotation is often used in conjunction with inline functions to control the visibility. | ||
Marked functions are used internally within the module but should not be exposed externally. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
content: | ||
- Introduction | ||
- Closure | ||
- noinline keyword | ||
- Visibility |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
fun main() { | ||
Main().highOrderFunction { | ||
println(it) | ||
} | ||
} | ||
|
||
class Main { | ||
fun highOrderFunction(lambda: (String) -> Unit) { | ||
val value = "value" | ||
lambda("Hello World") | ||
} | ||
|
||
private fun privateFunction() {} | ||
|
||
inline fun shinyFunction(inlineLambda: (String) -> Unit, noinline noInlineLambda: (String) -> Unit) { | ||
val x = noInlineLambda | ||
highOrderFunction { noInlineLambda("") } | ||
} | ||
} | ||
|
Oops, something went wrong.