Comparing XML and Compose Rendering Speed at Deep and Wide Nesting

Written by leonidivankin | Published 2022/06/24
Tech Story Tags: debugging | android | jetpack-compose | xml | benchmark | monitoring | code-refactoring | performance

TLDRThere are suggestions that these benchmarks do not give a complete picture of performance because they compare trivial solutions, such as single nesting or lack of a large number of views. That's why I decided to compare more complex variants: deep and wide view nesting; influence of hybrid (compose+xml) state with respect to these cases. This comparison in no way claims to be absolutely correct for all types of devices, amounts of RAM or different displays.via the TL;DR App

Abstract

There are many benchmarks that compare the performance and rendering speed of XML and compose. For the most part, they all show that compose is faster. However, there are suggestions that these benchmarks do not give a complete picture of performance because they compare trivial solutions, such as single nesting or lack of a large number of views. It is also assumed that in critical cases, such as deep or wide view nesting, there will be different results. There are also opinions that it doesn't make sense to refactor code to compose in already existing large project, because the hybrid variant (compose+xml) will exist in the project for a long time, and rendering performance will suffer even more because of it. That's why I decided to compare more complex variants:

-deep and wide view nesting;

-influence of hybrid (compose+xml) state with respect to these cases.

Observations and Assumptions

-This comparison in no way claims to be absolutely correct for all types of devices, amounts of RAM or different types of displays.

-Measurements were made on Xiaomi mi 5s phone, MIUI Global 10.2, 12.15GHz quad-core processor, 3GB RAM, Android 8.0. Especially chose not the most powerful and top-end phone, so that the difference was more noticeable. Most people use similar devices.

-To reduce the amount of code, I did not calculate the density of the screen, because I know that I have 3.

-Use the 16 colors suggested by Android Studio to separate the views from each other.

-The measurement for each case is taken 10 times and the average is calculated. -If you have any questions about quality or quantity, the complete test code is given after each example and you can repeat it at any time.

Introduction

I wanted to nest about 100 views into each other to make comparisons, but I ran into an xml parsing error: AAPT: error: failed to parse proto XML This error occurred when nesting more than 49 views. Therefore, in the comparisons below I have operated with exactly this number of nested elements. To visually display and control what is happening, I have assigned a different background color and some indents to each element. As you can see in the picture to the left, the red view has a raspberry view nested, the crimson view has a purple view nested, etc. This group of examples will show how deep nesting affects performance in different execution options: xml, code, compose, hybrid (xml+compose). These examples will be labeled as depth.

Along the way, I created another critical case - "very wide nesting". That is, I put also 49 elements in one view and colored them with different colors (see picture on the right). I purposely didn't use RecyclerView and LazyColumn for my comparisons. This group of examples will be marked as width. Each view is also colored to control what happens.

We now have the task of figuring out the rendering speed at each execution and answering a few questions:

a) will there be an advantage in using compose when nesting is deep and wide?

b) does the hybrid state affect rendering speed and performance in these cases?

We will use the segment between the two points for measurements. The start point after super.onCreate(savedInstanceState). Intermediate ― after super.onResume(). The final one is onWindowFocusChanged() which coincides with the full display of activity and the change of focus to the active state. The end point is after super.onResume(). The time will be measured by System.currentTimeMillis(), because we don't need to calculate absolute values, so there is no point in using more precise tools.

XML depth

Let's start with the first example. Let's put 49 times FrameLayout into each other and color the background in the standard colors. Let's add indents.

<!--49 times-->
<FrameLayout>
   <FrameLayout>
          <FrameLayout>
          ...
          </FrameLayout>
   </FrameLayout>
</FrameLayout>

Full code

https://gist.github.com/eef908bf788f36941beddc1cbc6d2348

https://gist.github.com/36b1e4870d86ae760ee238a3557d3d83

Table with the results:

XML width

Let's put the following example into LinearLayout 49 FrameLayout. I purposely didn't use RecyclerView to determine how the view would behave with wide nesting without optimizations.

<LinearLayout>
<!--49 times-->
   <FrameLayout/>
   <FrameLayout/>
   <FrameLayout/>
   ...
</LinearLayout>

Full code

https://gist.github.com/51b6b1e8dc288fb1decf7cbcb2ddd563

https://gist.github.com/504a474e66443813af1372c53bf316cc

Table with the results:

Code depth

In this example, we'll put 49 FrameLayout in, but in runtime. We'll also color it with standard colors and add indents. In this example, we know in advance that the xml will be faster because there is no xml parsing step. This example is very close to compose. Rather, the goal here is to find out how much influence legacy has accumulated over the years in View. As you will see below, the influence of legacy code is quite big.

//49 times
FrameLayout(this)
   FrameLayout(this)
       FrameLayout(this)
       ...

Full code

https://gist.github.com/48ade4ab6d6cde33a6e1d2f5b7aff95c

Table with the results:

Code width

In this example we will place the 49 FrameLayout vertically in the LinearLayout. Let's add colors to each element.

LinearLayout(this)
//49 times
   FrameLayout(this)
   FrameLayout(this)
   FrameLayout(this)

Full code

https://gist.github.com/808fd9ac925cf8f53260726f6e54ef09

Table with the results:

Compose Depth

In this example, let's finally start using compose. Let's nest Box 49 times inside each other to achieve deep nesting like in the xml example. Based on these two results, we will be able to compare whether deep nesting affects the rendering speed of the view hierarchy.

//49 times
Box()
    Box()
        Box()
        ...

Full code

https://gist.github.com/7fc700187c33d90ddf20bb4af4694a16

Table with the results:

Compose Width

In this example, as in the xml, we'll make a wide nesting by inserting 49 Boxes into one Column. Just like in the example above, we intentionally didn't use the LazyColumn. Based on this example and the example above in the xml, we can conclude whether wide nesting affects the speed of rendering.

Column()
//49 times
    Box()
    Box()
    Box()
    ...

Full code

https://gist.github.com/604ecdea027321df09d11778ce7d6bef

Table with the results:

Hybrid (xml + compose) depth

In this example, we put 24 FrameLayout, then we put ComposeView. In ComposeView we'll put 24 Boxes (24 FrameLayout + 1 ComposeView + 24 Box = 49). This way we can assess whether the hybrid state during refactoring affects the performance and the rendering speed of the view. Let's assume that all FrameLayout is the remaining legacy, and Box is the refactored part.

<!--24 times-->
<FrameLayout>
   <FrameLayout>
          <FrameLayout>
              ...
              <ComposeView>
              //24 times
              Box()
                  Box()
                      Box()
                      ...
              </ComposeView>
          </FrameLayout>
   </FrameLayout>
</FrameLayout>

Full code

https://gist.github.com/06020dc278669e72431fbb2b97b3b3b0

https://gist.github.com/789b7407559b84f92df8d6bf2220fd06

Table with the results:

Hybrid: xml + compose width

In this example, create 49 ComposeViews in a LinearLayout and put a Box in each one.

<!--49 times-->
<LinearLayout>
    <ComposeView>
        Box()
    </ComposeView>
    <ComposeView>
        Box()
    </ComposeView>
    <ComposeView>
        Box()
    </ComposeView>
        ...
<LinearLayout>

Full code

https://gist.github.com/9a6316c0d9e3be6f60076c858e4c1b02

https://gist.github.com/c9cee224fe8cddca71eb9559a9a385d6

Table with the results:

Conclusion

Let's summarize all the results in a graph.

The results were quite ambiguous. Compose turned out to be 50% faster than xml in the onPause() method, but 2 times slower in the onWindowFocusChanged() method.

Answering the two questions originally posed, we conclude:

  • There is an advantage with respect to the onPause() method, but not with respect to the onWindowFocusChanged() method.
  • With respect to the onPause() method, the hybrid state (xml + compose) does not affect the rendering speed, does not affect with respect to the onWindowFocusChanged() method with multiple horizontal ComposeView arrangement

Compose is a very powerful and useful tool, but as you can see, it won't solve all the problems for us. Using compose, like any other technology, requires compromises and accuracy in its application.


Written by leonidivankin | I'm an android developer
Published by HackerNoon on 2022/06/24