[HTML payload içeriği buraya]
32.3 C
Jakarta
Friday, May 15, 2026

turn out to be a greater Android developer with Compiler Explorer



Posted by Shai Barack – Android Platform Efficiency lead

Introducing Android help in Compiler Explorer

In a earlier weblog submit you discovered how Android engineers constantly enhance the Android Runtime (ART) in ways in which increase app efficiency on consumer gadgets. These modifications to the compiler make system and app code sooner or smaller. Builders don’t want to vary their code and rebuild their apps to profit from new optimizations, and customers get a greater expertise. On this weblog submit I’ll take you contained in the compiler with a instrument referred to as Compiler Explorer and witness a few of these optimizations in motion.

Compiler Explorer is an interactive web site for finding out how compilers work. It’s an open supply mission that anybody can contribute to. This 12 months, our engineers added help to Compiler Explorer for the Java and Kotlin programming languages on Android.

You need to use Compiler Explorer to know how your supply code is translated to meeting language, and the way high-level programming language constructs in a language like Kotlin turn out to be low-level directions that run on the processor.

At Google our engineers use this instrument to review totally different coding patterns for effectivity, to see how current compiler optimizations work, to share new optimization alternatives, and to show and be taught.
Studying is greatest when it’s performed by way of instruments, not guidelines. As an alternative of instructing builders to memorize totally different guidelines for methods to write environment friendly code or what the compiler would possibly or may not optimize, give the engineers the instruments to seek out out for themselves what occurs after they write their code in several methods, and allow them to experiment and be taught. Let’s be taught collectively!

Begin by going to godbolt.org. By default we see C++ pattern code, so click on the dropdown that claims C++ and choose Android Java. You must see this pattern code:

class Sq. {
   static int sq.(int num) {
       return num * num;
   }
}

screenshot of sample code in Compiler Explorer

click on to enlarge

On the left you’ll see a quite simple program. You would possibly say that it is a one line program. However this isn’t a significant assertion when it comes to efficiency – what number of strains of code there are doesn’t inform us how lengthy this program will take to run, or how a lot reminiscence might be occupied by the code when this system is loaded.

On the suitable you’ll see a disassembly of the compiler output. That is expressed when it comes to meeting language for the goal structure, the place each line is a CPU instruction. Wanting on the directions, we will say that the implementation of the sq.(int num) technique consists of two directions within the goal structure. The quantity and kind of directions give us a greater concept for how briskly this system is than the variety of strains of supply code. Because the goal structure is AArch64 aka ARM64, each instruction is 4 bytes, which implies that our program’s code occupies 8 bytes in RAM when this system is compiled and loaded.

Let’s take a short detour and introduce some Android toolchain ideas.

The Android construct toolchain (briefly)

a flow diagram of the Android build toolchain

Whenever you write your Android app, you’re sometimes writing supply code within the Java or Kotlin programming languages. Whenever you construct your app in Android Studio, it’s initially compiled by a language-specific compiler into language-agnostic JVM bytecode in a .jar. Then the Android construct instruments remodel the .jar into Dalvik bytecode in .dex recordsdata, which is what the Android Runtime executes on Android gadgets. Sometimes builders use d8 of their Debug builds, and r8 for optimized Launch builds. The .dex recordsdata go within the .apk that you simply push to check gadgets or add to an app retailer. As soon as the .apk is put in on the consumer’s system, an on-device compiler which is aware of the particular goal system structure can convert the bytecode to directions for the system’s CPU.

We will use Compiler Explorer to find out how all these instruments come collectively, and to experiment with totally different inputs and see how they have an effect on the outputs.

Going again to our default view for Android Java, on the left is Java supply code and on the suitable is the disassembly for the on-device compiler dex2oat, the final step in our toolchain diagram. The goal structure is ARM64 as that is the commonest CPU structure in use right this moment by Android gadgets.

The ARM64 Instruction Set Structure affords many directions and extensions, however as you learn disassemblies you can see that you simply solely must memorize a number of key directions. You’ll be able to search for ARM64 Fast Reference playing cards on-line that will help you learn disassemblies.

At Google we examine the output of dex2oat in Compiler Explorer for various causes, resembling:

    • Gaining instinct for what optimizations the compiler performs so as to consider methods to write extra environment friendly code.
    • Estimating how a lot reminiscence might be required when a program with this snippet of code is loaded into reminiscence.
    • Figuring out optimization alternatives within the compiler – methods to generate directions for a similar code which can be extra environment friendly, leading to sooner execution or in decrease reminiscence utilization with out requiring app builders to vary and rebuild their code.
    • Troubleshooting compiler bugs! 🐞

Compiler optimizations demystified

Let’s take a look at an actual instance of compiler optimizations in follow. Within the earlier weblog submit you’ll be able to examine compiler optimizations that the ART crew not too long ago added, resembling coalescing returns. Now you’ll be able to see the optimization, with Compiler Explorer!

Let’s load this instance:

class CoalescingReturnsDemo {
   String intToString(int num) {
       swap (num) {
           case 1:
               return "1";
           case 2:
               return "2";
           case 3:
               return "3";           
           default:
               return "different";
       }
   }
}

screenshot of sample code in Compiler Explorer

click on to enlarge

How would a compiler implement this code in CPU directions? Each case can be a department goal, with a case physique that has some distinctive directions (resembling referencing the particular string) and a few widespread directions (resembling assigning the string reference to a register and returning to the caller). Coalescing returns implies that some directions on the tail of every case physique will be shared throughout all circumstances. The advantages develop for bigger switches, proportional to the variety of the circumstances.

You’ll be able to see the optimization in motion! Merely create two compiler home windows, one for dex2oat from the October 2022 launch (the final launch earlier than the optimization was added), and one other for dex2oat from the November 2023 launch (the primary launch after the optimization was added). You must see that earlier than the optimization, the dimensions of the tactic physique for intToString was 124 bytes. After the optimization, it’s down to only 76 bytes.

That is in fact a contrived instance for simplicity’s sake. However this sample is quite common in Android code. For example contemplate an implementation of Handler.handleMessage(Message), the place you would possibly implement a swap assertion over the worth of Message#what.

How does the compiler implement optimizations resembling this? Compiler Explorer lets us look contained in the compiler’s pipeline of optimization passes. In a compiler window, click on Add New > Decide Pipeline. A brand new window will open, exhibiting the Excessive-level Inner Illustration (HIR) that the compiler makes use of for this system, and the way it’s reworked at each step.

screenshot of the high-level internal representation (HIR) the compiler uses for the program in Compiler Explorer

click on to enlarge

Should you take a look at the code_sinking go you will notice that the November 2023 compiler replaces Return HIR directions with Goto directions.

A lot of the passes are hidden when Filters > Disguise Inconsequential Passes is checked. You’ll be able to uncheck this selection and see all optimization passes, together with ones that didn’t change the HIR (i.e. don’t have any “diff” over the HIR).

Let’s examine one other easy optimization, and look contained in the optimization pipeline to see it in motion. Contemplate this code:

class ConstantFoldingDemo {
   static int demo(int num) {
       int outcome = num;
       if (num == 2) {
           outcome = num + 2;
       }
       return outcome;
   }
}

The above is functionally equal to the under:

class ConstantFoldingDemo {
   static int demo(int num) {
       int outcome = num;
       if (num == 2) {
           outcome = 4;
       }
       return outcome;
   }
}

Can the compiler make this optimization for us? Let’s load it in Compiler Explorer and switch to the Decide Pipeline Viewer for solutions.

screenshot of Opt Pipeline Viewer in Compiler Explorer

click on to enlarge

The disassembly exhibits us that the compiler by no means bothers with “two plus two”, it is aware of that if num is 2 then outcome must be 4. This optimization known as fixed folding. Contained in the conditional block the place we all know that num == 2 we propagate the fixed 2 into the symbolic identify num, then fold num + 2 into the fixed 4.

You’ll be able to see this optimization occurring over the compiler’s IR by choosing the constant_folding go within the Decide Pipeline Viewer.

Kotlin and Java, facet by facet

Now that we’ve seen the directions for Java code, attempt altering the language to Android Kotlin. You must see this pattern code, the Kotlin equal of the fundamental Java pattern we’ve seen earlier than:

enjoyable sq.(num: Int): Int = num * num

screenshot of sample code in Kotlin in Compiler Explorer

click on to enlarge

You’ll discover that the supply code is totally different however the pattern program is functionally an identical, and so is the output from dex2oat. Discovering the sq. of a quantity leads to the identical directions, whether or not you write your supply code in Java or in Kotlin.

You’ll be able to take this chance to review attention-grabbing language options and uncover how they work. For example, let’s evaluate Java String concatenation with Kotlin String interpolation.

In Java, you would possibly write your code as follows:

class StringConcatenationDemo {
   void stringConcatenationDemo(String myVal) {
       System.out.println("The worth of myVal is " + myVal);
   }
}

Let’s learn how Java String concatenation truly works by attempting this instance in Compiler Explorer.

screenshot of sample code in Kotlin in Compiler Explorer

click on to enlarge

First you’ll discover that we modified the output compiler from dex2oat to d8. Studying Dalvik bytecode, which is the output from d8, is often simpler than studying the ARM64 directions that dex2oat outputs. It’s because Dalvik bytecode makes use of larger degree ideas. Certainly you’ll be able to see the names of sorts and strategies from the supply code on the left facet mirrored within the bytecode on the suitable facet. Attempt altering the compiler to dex2oat and again to see the distinction.

As you learn the d8 output you could understand that Java String concatenation is definitely applied by rewriting your supply code to make use of a StringBuilder. The supply code above is rewritten internally by the Java compiler as follows:

class StringConcatenationDemo {
   void stringConcatenationDemo(String myVal) {
       StringBuilder sb = new StringBuilder();
       sb.append("The worth of myVal is ");
       sb.append(myVal);
       System.out.println(sb.toString());
  }
}

In Kotlin, we will use String interpolation:

enjoyable stringInterpolationDemo(myVal: String) {
   System.out.println("The worth of myVal is $myVal");
}

The Kotlin syntax is less complicated to learn and write, however does this comfort come at a price? Should you do that instance in Compiler Explorer, you could discover that the Dalvik bytecode output is roughly the identical! On this case we see that Kotlin affords an improved syntax, whereas the compiler emits related bytecode.

At Google we examine examples of language options in Compiler Explorer to study how high-level language options are applied in lower-level phrases, and to higher inform ourselves on the totally different tradeoffs that we’d make in selecting whether or not and methods to undertake these language options. Recall our studying precept: instruments, not guidelines. Moderately than memorizing guidelines for a way it is best to write your code, use the instruments that can allow you to perceive the upsides and disadvantages of various alternate options, after which make an knowledgeable choice.

What occurs while you minify your app?

Talking of constructing knowledgeable selections as an app developer, try to be minifying your apps with R8 when constructing your Launch APK. Minifying usually does three issues to optimize your app to make it smaller and sooner:

      1. Lifeless code elimination: discover all of the reside code (code that’s reachable from well-known program entry factors), which tells us that the remaining code isn’t used, and subsequently will be eliminated.

      2. Bytecode optimization: varied specialised optimizations that rewrite your app’s bytecode to make it functionally an identical however sooner and/or smaller.

      3. Obfuscation: renaming all sorts, strategies, and fields in your program that aren’t accessed by reflection (and subsequently will be safely renamed) from their names in supply code (com.instance.MyVeryLongFooFactorySingleton) to shorter names that slot in much less reminiscence (a.b.c).

Let’s see an instance of all three advantages! Begin by loading this view in Compiler Explorer.

screenshot of sample code in Kotlin in Compiler Explorer

click on to enlarge

First you’ll discover that we’re referencing sorts from the Android SDK. You are able to do this in Compiler Explorer by clicking Libraries and including Android API stubs.

Second, you’ll discover that this view has a number of supply recordsdata open. The Kotlin supply code is in instance.kt, however there may be one other file referred to as proguard.cfg.

-keep class MinifyDemo {
   public void goToSite(...);
}

Wanting inside this file, you’ll see directives within the format of Proguard configuration flags, which is the legacy format for configuring what to maintain when minifying your app. You’ll be able to see that we’re asking to maintain a sure technique of MinifyDemo. “Conserving” on this context means don’t shrink (we inform the minifier that this code is reside). Let’s say we’re creating a library and we’d like to supply our buyer a prebuilt .jar the place they’ll name this technique, so we’re holding this as a part of our API contract.

We arrange a view that can allow us to see the advantages of minifying. On one facet you’ll see d8, exhibiting the dex code with out minification, and on the opposite facet r8, exhibiting the dex code with minification. By evaluating the 2 outputs, we will see minification in motion:

      1. Lifeless code elimination: R8 eliminated all of the logging code, because it by no means executes (as DEBUG is at all times false). We eliminated not simply the calls to android.util.Log, but additionally the related strings.

      2. Bytecode optimization: for the reason that specialised strategies goToGodbolt, goToAndroidDevelopers, and goToGoogleIo simply name goToUrl with a hardcoded parameter, R8 inlined the calls to goToUrl into the decision websites in goToSite. This inlining saves us the overhead of defining a technique, invoking the tactic, and getting back from the tactic.

      3. Obfuscation: we advised R8 to maintain the general public technique goToSite, and it did. R8 additionally determined to maintain the tactic goToUrl because it’s utilized by goToSite, however you’ll discover that R8 renamed that technique to a. This technique’s identify is an inside implementation element, so obfuscating its identify saved us a number of valuable bytes.

You need to use R8 in Compiler Explorer to know how minification impacts your app, and to experiment with alternative ways to configure R8.

At Google our engineers use R8 in Compiler Explorer to review how minification works on small samples. The authoritative instrument for finding out how an actual app compiles is the APK Analyzer in Android Studio, as optimization is a whole-program drawback and a snippet may not seize each nuance. However iterating on launch builds of an actual app is gradual, so finding out pattern code in Compiler Explorer helps our engineers shortly be taught and iterate.

Google engineers construct very giant apps which can be utilized by billions of individuals on totally different gadgets, so that they care deeply about these sorts of optimizations, and try to take advantage of use out of optimizing instruments. However lots of our apps are additionally very giant, and so altering the configuration and rebuilding takes a really very long time. Our engineers can now use Compiler Explorer to experiment with minification below totally different configurations and see leads to seconds, not minutes.

You might surprise what would occur if we modified our code to rename goToSite? Sadly our construct would break, except we additionally renamed the reference to that technique within the Proguard flags. Luckily, R8 now natively helps Preserve Annotations as a substitute for Proguard flags. We will modify our program to make use of Preserve Annotations:

@UsedByReflection(variety = KeepItemKind.CLASS_AND_METHODS)
public static void goToSite(Context context, String website) {
    ...
}

Right here is the full instance. You’ll discover that we eliminated the proguard.cfg file, and below Libraries we added “R8 keep-annotations”, which is how we’re importing @UsedByReflection.

At Google our engineers choose annotations over flags. Right here we’ve seen one good thing about annotations – holding the details about the code in a single place relatively than two makes refactors simpler. One other is that the annotations have a self-documenting facet to them. For example if this technique was stored usually because it’s referred to as from native code, we’d annotate it as @UsedByNative as an alternative.

Baseline profiles and also you

Lastly, let’s contact on baseline profiles. To date you noticed some demos the place we checked out dex code, and others the place we checked out ARM64 directions. Should you toggle between the totally different codecs you’ll discover that the high-level dex bytecode is rather more compact than low-level CPU directions. There may be an attention-grabbing tradeoff to discover right here – whether or not, and when, to compile bytecode to CPU directions?

For any program technique, the Android Runtime has three compilation choices:

      1. Compile the tactic Simply in Time (JIT).

      2. Compile the tactic Forward of Time (AOT).

      3. Don’t compile the tactic in any respect, as an alternative use a bytecode interpreter.

Operating code in an interpreter is an order of magnitude slower, however doesn’t incur the price of loading the illustration of the tactic as CPU directions which as we’ve seen is extra verbose. That is greatest used for “chilly” code – code that runs solely as soon as, and isn’t vital to consumer interactions.

When ART detects {that a} technique is “sizzling”, will probably be JIT-compiled if it’s not already been AOT compiled. JIT compilation accelerates execution occasions, however pays the one-time value of compilation throughout app runtime. That is the place baseline profiles are available in. Utilizing baseline profiles, you because the app developer may give ART a touch as to which strategies are going to be sizzling or in any other case price compiling. ART will use that trace earlier than runtime, compiling the code AOT (often at set up time, or when the system is idle) relatively than at runtime. This is the reason apps that use Baseline Profiles see sooner startup occasions.

With Compiler Explorer we will see Baseline Profiles in motion.

Let’s open this instance.

screenshot of sample code in Compiler Explorer

click on to enlarge

The Java supply code has two technique definitions, factorial and fibonacci. This instance is about up with a handbook baseline profile, listed within the file profile.prof.txt. You’ll discover that the profile solely references the factorial technique. Consequently, the dex2oat output will solely present compiled code for factorial, whereas fibonacci exhibits within the output with no directions and a dimension of 0 bytes.

Within the context of compilation modes, which means factorial is compiled AOT, and fibonacci might be compiled JIT or interpreted. It’s because we utilized a unique compiler filter within the profile pattern. That is mirrored within the dex2oat output, which reads: “Compiler filter: speed-profile” (AOT compile solely profile code), the place earlier examples learn “Compiler filter: pace” (AOT compile the whole lot).

Conclusion

Compiler Explorer is a superb instrument for understanding what occurs after you write your supply code however earlier than it may well run on a goal system. The instrument is straightforward to make use of, interactive, and shareable. Compiler Explorer is greatest used with pattern code, however it goes by way of the identical procedures as constructing an actual app, so you’ll be able to see the influence of all steps within the toolchain.

By studying methods to use instruments like this to find how the compiler works below the hood, relatively than memorizing a bunch of guidelines of optimization greatest practices, you may make extra knowledgeable selections.

Now that you’ve got seen methods to use the Java and Kotlin programming languages and the Android toolchain in Compiler Explorer, you’ll be able to degree up your Android improvement abilities.

Lastly, do not forget that Compiler Explorer is an open supply mission on GitHub. If there’s a function you’d prefer to see then it is only a Pull Request away.

Java and OpenJDK are emblems or registered emblems of Oracle and/or its associates.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles