Performance Best Practices

The following are the recommended best practices for optimizing performance on You.i React Native-based apps:

General Performance Tips

Set Dev Flag to False

When a bundle is loaded, there is the option to set the mode the application should run in. It is recommended to run your application in release mode (dev=false). When running in development mode, there is additional work being done to provide good messaging to the developer, including validating propTypes and other assertions. It is also recommended to profile performance of your application in release mode.

Set Minify to True

Ensure that the app is running with the minified=true flag.

Strip console.log Statements

Logging out information to the console slows down the javascript thread and may cause a bottleneck depending on the frequency of the logs. It is recommended to not log information in release mode. Logging may also come from third-party libraries. You can remove them via a babel plugin.

The console.log statements, especially the ones that do string concatenation, can be very expensive, especially on low-end devices. Remove them for better performance.

Babel Plugins

Depending on how your app is written, you may see a significant performance improvement by making use of the @babel/plugin-transform-react-constant-elements package.

Optimizing List Performance

You can do the following to improve the performance of lists in your app:

Set initialNumToRender Prop

As the initialNumToRender prop sets the number of items to render on the first pass, therefore, set it to 1.5 x the number of items that can fit on the screen at once for faster load times.

Using windowSize to Enable Item Streaming

The windowSize prop determines the maximum number of items rendered outside of the visible area, in units of visible lengths. So if your list fills the screen, then windowSize={21} will render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing this number will reduce memory consumption and may improve performance, but will increase the chance that fast scrolling may reveal momentary blank areas of un-rendered content.

Setting it to 4 can provide serious performance and load time improvements. However, it should be noted that you must also define the getItemLayout prop so that layout will be calculated correctly.

Using getItemLayout to Calculate Layout

To allow FlatList to correctly calculate layout, you need to know the total height and width of the list items outside the currently visible viewport, and pass them in using the getItemLayout prop. The height specified in an AE composition will not be able to be mapped directly. Below are some utility functions to convert composition height in AE to its height needed for getItemLayout:

calcItemLayoutHeight(height) {
  return height * (Dimensions.get('window').height / 1080);
}

For horizontal lists, you can use this to get the width:

calcItemLayoutWidth(width) {
  return width * (Dimensions.get('window').width / 1920);
}

For the best results, ensure your lists have a uniform item size.

Using Keys

It is recommended to use keys for list items so that they will not be re-rendered when an item is added or removed from the list. Otherwise the list will not know which items have changed, and will update everything. Keys should be a string explicitly.

Optimize Timelines

Removing ImageSet timelines on image assets drastically increases performance of lists.

Optimizing Navigation Performance

React Navigation is the de facto navigation framework for React Native apps. At the moment there are no performance improvements to be had from it, but some things to be aware of:

  • The navigation framework does not remove nodes from the scene tree when navigating to another page, as a result, all created screens and their nodes are always in the scene tree.
  • The deeper the stack goes for an app, and the more complicated each screen, the more nodes there will be.
  • It uses the opacity attribute to hide the previous screens.

As a result, it is recommended to:

  • Have simple screens or simplify the UI as much as possible for each screen; and
  • Avoid deep screen stacks within your app.

Optimizing Re-Render Performance

You can do the following to optimize the re-renders in your app:

Component vs PureComponent

Most custom React Components extend from Components instead of PureComponents. The main difference between the two is the shouldComponentUpdate method contents. For a Component, each time the props or state changes, it will re-render, even if they are the same. A PureComponent will do a shallow comparison between the old state, new state, old props and new props and determine if a re-render will occur. It is recommended to use a PureComponent as the default to extend from. Components should only be extended if the user intends to implement their own custom shouldComponentUpdate logic.

State Variables

A re-render will occur each time a state variable is changed. As a result, state variables should only contain graphical values, such as colors, border styles, text content, etc. You may also consider extending from a Component and implementing the shouldComponentUpdate method if you require fine control over when a re-render should occur, or extending from a PureComponent otherwise.

Prop Variables

A re-render will occur each time the prop values for a component is changed. The component should only be passed the props that it needs. You may also consider extending from a Component and implementing the shouldComponentUpdate method if you require fine control over when a re-render should occur, or extending from a PureComponent otherwise.

Redux Store

A re-render will occur each time the prop values for a component is changed. If your application is making use of redux, it is recommended to have a child component access a prop in the store instead of passing that prop to the child. As a result, re-renders will be avoided.

Inline Functions

An inline function is a function that is defined within the render method of a component and passed down to a child component as a prop. For example, functions are referred to by pointers. When an inline function is created, a new local instance of a pointer to that function is created. As a result, the prop called onPress will receive a new pointer each time the state is updated, which causes a re-render of the Button component. Therefore, it is recommended to avoid using inline functions, and instead provide references.

Use why-did-you-update

Why-did-you-update is a third-party tool that allows you to console log when unnecessary re-renders occur. You.i TV recommends that you install version 0.2.0, and only enable it for debug builds.

Componentization

Break your app into small components to reduce re-renders.

Optimizing Text Performance

Remember the following points for optimizing text in a You.i React Native based app:

Optimizing Text Rendering

To improve the performance of text rendering, you can pregenerate text data files. See Pregenerating Text Data Files.

Optimizing Text Types

Remember the following:

  • When scale is not being animated, SDF text is more costly to render, and atlas text should be used.
  • If text is being scaled, the SDF text is more optimal.
  • Most notably, the metadata container has all of its description text marked as SDF. It should be changed.

Optimize Atlas Size

To optimize the size of your text Atlas, you need to override CYIApp::UserConfiguration(), then make a CYIFrameworkConfiguration instance, call SetTextAtlasSize on it, and return it. See the following example:

CYIFrameworkConfiguration MyApp::UserConfiguration()
{
    CYIFrameworkConfiguration configuration;
    configuration.SetTextAtlasSize(512);
    return configuration;
}

Optimizing Performance by Reducing Overdraw

Overdraw occurs when a screen pixel is drawn more than once. A single pixel overdrawn doesn’t have much impact, but an entire screen being overdrawn can severely hamper performance of your app. There are a few tools and techniques you can use to detect when overdraw is happening and reduce it.

Measuring Overdraw

As of You.i Platform 5.1.0, you will be able to view the current overdraw of an app using a Development Panel widget. When active, the render will appear as in the image below:

Image

The rendered colors refer to different levels of overdraw:

  • White: drawn zero or one times (no overdraw)
  • Blue: drawn twice (overdrawn once)
  • Green: drawn three times (overdrawn twice)
  • Light Red: drawn four times (overdrawn thrice)
  • Dark Red and shades of Purple: drawn five or more times (overdrawn four or more times)

When the Overdraw Widget is active, the overdraw rating (an average of overdraw over an entire screen) is visible in the top right corner of the heads up display or HUD. The HUD is the black display bar at the top and bottom right of a development panel display:

Image

You.i TV recommends that you keep this value below 2.5 to avoid performance impacts. In the visual overdraw display, this means avoiding any red or purple areas. Small sections of red are fine, but large areas of red or purple would indicate excessive overdraw.

Be aware that displaying the overdraw visually can have a heavy impact on performance. This can be avoided by displaying the overdraw in the HUD only.

To display overdraw in the HUD only:

  1. Open the HUD sub-menu in the Dev Panel.
  2. Scroll down to the Overdraw option and click on it.

Note While Android natively supports displaying overdraw, the reported values are not accurate because You.i Platform does its rendering through an Open GL surface rather than through Android views.

Reducing Overdraw

Reducing overdraw can be as simple as removing unnecessary (non-visible) items from a scene. Other times, reducing overdraw is more difficult. In either case, make use of the Overdraw Dev Panel widget to view where overdraw can be reduced.

Generally, it is best to focus on full-screen (or near-full-screen) items. Small spots with high overdraw are fine if the overall overdraw remains low (2.5 and under). However, small compositions with high overdraw count can significantly impact performance if those small compositions are repeated. For example, a poster asset card with a high overdraw value could significantly contribute to the overall overdraw as posters are often shown in lists.

The following are several techniques you can use to help reduce overdraw in your apps.

Use Background Colors

The easiest way to reduce overdraw is to avoid full-screen solids. These are often used to set a background color on a scene. Instead, set a color on the root composition in After Effects, and then add that scene to the app with LayerType::Opaque.

The color of an After Effects composition is set through the Composition Settings:

Image

The scene is then loaded like this:

GetSceneManager()->AddScene("MainComp",
    std::move(pSceneView), 0, CYISceneManager::LayerType::Opaque);

This should only be done for full-screen scenes, as loading scenes with LayerType::Opaque prevents the scene from having transparency.

Combine Static Image Layers

Where possible, image layers should be combined into a single image to reduce overdraw. Consider the following After Effects composition:

Image

Here the designer wants to get an ‘old timey’ look, so a transparent frame is added as well as an adjustment layer to simulate sepia:

Image

This works, but significantly increases the overdraw of the scene. The initial scene with the image had an overdraw of 0.0 (since each pixel was drawn once only). The new scene, however, has an overdraw of 3.0—here the effects are particularly expensive.

If the image is known to be static, overdraw can be reduced by combining every layer into a single image. This would typically be done in Photoshop or in a similar tool. This would bring the overdraw back to 0.0, and would look identical.

Another common case where it would be beneficial to combine layers would be for static images with gradients. Combining the image with the gradient means that each pixel is only drawn once rather than twice.

Note It is generally not worth combining images when those images only cover a small portion of the screen.

Hide Image Placeholders

Image views often have ‘placeholder’ graphics that are displayed while assets are downloading or decoding. When the image that should be displayed is available, the image is often animated in. However, that animation often leaves the placeholder graphic around, even if it is no longer visible. In order to avoid affecting overdraw, that placeholder graphic should be hidden somehow.

Consider the following three image views:

Image

The first image view is a ‘plain’ image view without animation. The second is an image view with a placeholder and an opacity animation to ‘show’ the assigned image. The third is an image view demonstrating the technique being described here. Before an image is assigned, all three image views have the same overdraw:

Image

However, after assigning an image, the overdraw of the second increases as the ‘new’ image node is revealed, overlaying itself onto the placeholder:

Image

Note that the overdraw of the third image view (after the animation has completed) does not change. The way to implement this is to make the ‘reveal’ animation one frame shorter than its marker, and place a pair of ‘opacity’ keyframes on the placeholder layer(s) to hide that layer when the ‘real’ image has been fully animated.

The animation timeline ends up looking something like this:

Image

While this tip is about image views specifically, it also applies to any animation that reveals (or hide) other compositions/layers. To keep overdraw low, always hide compositions that are fully overlaid by other opaque compositions. Note that either opacity or the visibility track can be used to hide layers for purposes of reducing overdraw.

Unstage Unused Scenes

C++ applications generally use the screen transition manager to transition between scenes. As a scene is transitioned out, that scene is automatically unstaged. However, showing overlays generally does not use the scene transition manager. As a result, the scene ‘underneath’ the overlay does not get unstaged and is still rendered. When using full screen overlays, it can be beneficial to manually hide (or unstage) the ‘bottom’ scene once the overlay has finished animating in. Just remember to show/stage the scene when dismissing the overlay!

Crop Transparent Parts of Images

Sometimes, image assets are created unnecessarily large. Note that even if an image pixel is completely transparent, it still gets rendered and counts towards overdraw. Consider the following image:

Image

That image should be cropped such that the edges of the image only have non-fully-transparent pixels:

Image

This image is approximately five times smaller, and thus results in five times less pixels drawn on the screen. As a bonus, that image also takes up less memory.

Don’t Rely on Clipping

Clipping is sometimes used to hide parts of compositions. However, on some platforms, clipped parts still get rendered (and discarded), and thus count towards overdraw.

Two things should be done to avoid incurring overdraw when clipping is used:

  • Avoid oversized solids that get clipped. Instead, resize or rescale the solids so that they don’t go past the clipping boundaries.
  • Avoid oversized images that get clipped. Instead, clip those images in Photoshop, so that they also don’t go past the clipping boundaries.

Use Solids for Borders

Buttons and asset cards often have borders. Sometimes those borders are always visible, and sometimes they’re only displayed when a given button or asset card is focused. Borders could be done by using a transparent image like this:

Image

Instead of using such an image, a border can be constructed from four solids. The following image shows how this is done (with separate coloring for each ‘side’ to make the structure more obvious):

Image

Again, this is only worth doing when the surface covered by the bordered items is significant.

Optimizing Performance by Reducing Node Count

The following topics explain how to reduce node counts in your App:

Simplify User Interface (UI)

Creating and deleting nodes are expensive operations, and the larger the tree is, the more time it takes for all three trees to keep in sync with one another and update appropriately. As a result, designers should strive to reduce the number of layers in their AE designs, and developers should strive to reduce the number of JSX tags.

Layering Elements

It is recommended to avoid layering many items on top of each other, for example, multiple full-screen backgrounds. On lower-end devices this would drop the performance drastically.

Conditional Rendering

Conditional rendering is rendering only the components needed based on a set of conditions. It is similar to making use of the display:none attribute in CSS. If a component does not need to be shown, and it is not shown very often, conditional rendering should be used rather than making use of the opacity or the visibility attribute where possible. If conditional rendering is used, and the component does not need to be shown, the component will not be added to the react trees, and will therefore reduce the number of nodes. For example, instead of setting a loading screen to be hidden (i.e., visible=false), it can be conditionally rendered based on whether the content has loaded or not.

Note This performance improvement is only intended for nodes that do not need to be shown or hidden frequently. If your node frequently toggles between being shown or hidden state, it is recommended to use the visibility or opacity properties instead of creating or deleting nodes.

The following table explains the difference between opacity, visibility, and conditional rendering:

Property Occupies Space Consumes Click Nodes Exist
CSS (opacity:0)
You.i React Native (opacity=0)
CSS (visibility:hidden)
You.i React Native (visible=false)
CSS (display:none)
You.i React Native (conditional rendering)