Kotlin 1.4 lazy optimization

Kotlin_1.4_lazy_optimization
Kotlin 1.4 lazy optimization

~ 5 minutes read featured in kotlin weekly issue #193
no time? jump straight to conclusion

Lazy is one of the delegated properties kotlin offers. Depending on which version of kotlin you use the lazy function call is compiled differently.
This post is about how Kotlin 1.4M1 optimizes the lazy function compilation and how you can leverage the optimization also in Kotlin 1.3.

A lot of Kotlin 1.3 (tested with 1.3.60/61) code that contains lazy function calls get compiled into classes that contain KProperties arrays.

 

A one liner like this:

val myLazyString:String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { "hi" }

source

can be compiled and decompiled. The decompiled code (using procyon) would be:

    static final /* synthetic */ KProperty[] $$delegatedProperties;
    @NotNull
    private final Lazy myLazyString$delegate;
    
    static {
        $$delegatedProperties = new KProperty[] {
        (KProperty)Reflection.property1((PropertyReference1)
        new PropertyReference1Impl((KDeclarationContainer)
                Reflection.getOrCreateKotlinClass((Class)
                        Dearlazy.class), 
        "myLazyString", "getMyLazyString()Ljava/lang/String;")) };
    }
    
    @NotNull
    public final String getMyLazyString() {
        final Lazy myLazyString$delegate = this.myLazyString$delegate;
        final KProperty kProperty = Dearlazy.$$delegatedProperties[0];
        return (String)myLazyString$delegate.getValue();
    }

more decompilation details

The decompiled classes code includes a KProperty array $$delegatedProperties and a lazy object (see also KotlinConf 2019 presentation: Kotlin Uncovered by Chet Haase & Romain Guy (lazy)). In this specific example, such an array might not be needed because the string definition within the lazy function call is just a string.

Kotlin 1.4M1 lazy optimization

Using Kotlin 1.4M1 you can compile the same one liner kotlin code as above. The decompiled class would be:

    @NotNull
    private final Lazy myLazyString$delegate;
    
    @NotNull
    public final String getMyLazyString() {
        return (String)this.myLazyString$delegate.getValue();
    }
    
    public Dearlazy() {
        this.myLazyString$delegate = 
            LazyKt.lazy(LazyThreadSafetyMode.SYNCHRONIZED, 
                (Function0)Dearlazy$myLazyString.
                    Dearlazy$myLazyString$2.INSTANCE);
    }

more decompilation details

While the lazy object remains, the KProperty array is not part of the decompiled code it anymore. The code looks much slimmer.

Can I use the Kotlin 1.4M1 lazy optimization in Kotlin 1.3 as well?

Yes. My test results about compiling and decompiling with Kotlin 1.3.70 & Kotlin 1.4M1 are the same:

    @NotNull
    private final Lazy myLazyString$delegate;
    
    @NotNull
    public final String getMyLazyString() {
        return (String)this.myLazyString$delegate.getValue();
    }
    
    public Dearlazy() {
        this.myLazyString$delegate = 
            LazyKt.lazy(LazyThreadSafetyMode.SYNCHRONIZED, 
                (Function0)Dearlazy$myLazyString.
                    Dearlazy$myLazyString$2.INSTANCE);
    }

more decompilation details: 1.3.70 && 1.4M1

Side track: JVM decompiler

There are multiple options to compile and decompile Kotlin code.
I used gradle 5+ to compile Kotlin code in this context. For class decompilation I used procyon. This is simply because I like the syntax highlighting procyon offers.

That comes with a price. A terminal command:

cd build/classes/kotlin/main/info/lotharschulz/lazy && procyon Dearlazy && cd ../../../../../../..

produced the results I shared above.

partial procyon output:

cd build/classes/kotlin/main/info/lotharschulz/lazy && procyon Dearlazy && cd ../../../../../../..

However, a call like

procyon build/classes/kotlin/main/info/lotharschulz/lazy/Dearlazy

produces different results. Those did not allow the analysis above.

partial procyon output:

procyon build/classes/kotlin/main/info/lotharschulz/lazy/Dearlazy

In comparison, javap produced the same results regardless which of the 2 following calls I used:

javap -c -p build/classes/kotlin/main/info/lotharschulz/lazy/Dearlazy
javap output:

javap -c -p build/classes/kotlin/main/info/lotharschulz/lazy/Dearlazy
cd build/classes/kotlin/main/info/lotharschulz/lazy/ && javap -c -p Dearlazy 
javap output:

cd build/classes/kotlin/main/info/lotharschulz/lazy/ && javap -c -p Dearlazy

I rated syntax highlighting higher that saving cd commands. That is a personal choice. I found java decompiler interesting as one of a lot of java decompiler listings.

Conclusion

Kotlin 1.4M optimizes delegated properties. Lazy functions as one of the delegated properties are compiled into much slimmer classes – arrays like $$delegatedProperties will not be generated if not needed.

According to my test, you can leverage this optimization with Kotlin 1.3.70 as well.

Links

Update 2020 04 15

  • fix the link in Kotlin 1.3.71 and changed the tag to Kotlin 1.3.70
  • Kotlin 1.3.72 link added

1 Comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.