|
| 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 | +
|
0 commit comments