Consistently Improve App Performance With DMAIC and Reassure

Hanna Sobolewska
Callstack Engineers
12 min readFeb 22, 2023

by Tomasz Misiukiewicz

screen with a diagram showing improved performance

The following article is part of The Ultimate Guide to React Native Optimization and explains how the DMAIC process and tools like Reassure can help keep your app performant over time.

Why is this important?

You want your React Native app to perform well and fast at all times to keep the user experience optimal. That’s impossible unless you make performance regression monitoring a part of your app development and maintenance process. In this article, we explain how the DMAIC process can help your React Native team consistently improve app performance. We also look at the tools you can use to ensure your app remains performant as it grows, mainly Reassure.

In other blog posts based on The Ultimate Guide to React Native Optimization, we touch on the following stability-related topics:

Why should you keep improving app performance?

Customers have very little patience for slow apps. According to the Unbounce report, nearly 70% of consumers admit that page speed influences their willingness to buy. Good examples here are Walmart and Amazon — both of these companies noticed an increase in revenue by up to 1% for every 100 milliseconds of load time improvement.

Performance has a noticeable impact on your business goals, yet it’s often the case that after fixing one performance issue, the app sooner or later gets slow again. As such a scenario can take a toll on your organization, you need to not only fix performance issues, but also make sure they don’t happen again.

Use DMAIC process to consistently improve React Native app performance

You want your React Native app to perform well and fast at all times, but how do you achieve this goal? From the technical perspective, it boils down to avoiding guesswork and making data-informed decisions. We should also remember that improving performance is a process; it’s impossible to fix everything at once, but small steps can provide big results.

Developing an app is a process, and so is performance improvement. Most important, though, every process can be optimized. In the case of improving React Native app performance and performance regression monitoring, one of the most effective ways of doing so is following the DMAIC methodology. It’s a very data-driven and well-structured approach comprising the following steps: Define, Measure, Analyze, Improve, and Control. Let’s see how we can apply each phase in our apps.

Define

In this phase of the DMAIC process, we should focus on defining the problem, what we want to achieve, opportunities for improvement, etc. Listening to the customers’ expectations and feedback in this phase is essential, as it helps better understand their needs, preferences, and problems.

Next, it is very important to measure it somehow. Let’s say the customer wants a fast checkout. After analyzing the components, we know that we need a swift checkout process, a short wait time, and smooth animations and transitions to achieve this. All of these points can be decomposed into measurable CTQs (Critical-to-Quality). For example, a short wait time can be decomposed into a quick server response and a low number of server errors. Another handy tool is analyzing common user paths. With good tracking, we can understand what parts of the app are mostly used by the users.

Choosing priorities and defining the order in which we will optimize things should conclude this phase. Any tools and techniques for prioritizing will definitely help here. If you want to improve app performance efficiently, setting measurable goals and putting them in the project scope should be your go-to’s at this stage.

Measure

Since we already know our direction, it’s time to assess the starting point. It’s all about collecting as much data as possible to get the actual picture of the problem. We need to ensure the measurement process is precise. Creating a data collection plan and engaging the development team to build the metrics is really helpful. Then, it’s time to do some profiling.

When profiling in React Native, the main question is whether to do this on JavaScript or the native side. It heavily depends on the app’s architecture, but most of the time, it’s a mix of both. One of the most popular tools is React Profiler, which allows us to wrap a component to measure the render time and the number of renders. It’s very helpful because many performance issues come from unnecessary rerenders.

Discover how to use it here:

import React, { Profiler } from "react";
import { View } from "react-native";

const Component = () => (
<Profiler id="Component" onRender={(...args) => console.log(args)}>
<View />
</Profiler>
);

export default Component;

Using React Profiler API.tsx hosted with ❤ by GitHub

It will output the data:

{
id: "Component",
phase: "mount",
actualDuration: 1.3352311453,
baseDuration: 0.95232323318,
...
}

Output of the React Profiler API.tsx hosted with ❤ by GitHub

The second tool is a library created by Shopify called React Native Performance. It allows you to place some markers in the code and measure the execution time. There is also a pretty nice Flipper plugin that helps to visualize the output:

charts showing blank spaces
Source: https://shopify.github.io/react-native-performance/docs/guides/flipper-react-native-performance

Speaking of Flipper, it has some more plugins that help us to measure the app performance and speed up the development process. We can use React Native Performance Monitor Plugin for a Lighthouse-like experience or React Native Performance Lists Profiler Plugin.

On the native side, the most common method is using Native IDEs: Xcode and Android Studio. There are plenty of useful insights which can be analyzed and lead to some conclusions and results.

The most important aspect of the measure phase is measurement variation. Due to different environments, we have to be very careful when profiling. Even if the app is run on the same device, some external factors might affect performance measurements. That’s why we should base all the measurements on release builds.

Analyze

This phase of the DMAIC process aims to find the root cause of our problem. It’s a good idea to start with a list of things that could potentially cause the issue. A little brainstorming with a team can be helpful here.

One of the most popular tools to define a problem is called a cause and effect diagram. It looks like a fish, and we should draw it from right to left. We start from the head that contains the problem statement (we should already have it based on the define phase). Then, we identify all the potential major causes of the problem and assign them to the fish bones. Next, we assign all the possible causes to each major cause. Since there are many things that can impact app performance, it’s important to narrow our list down. Try to outline and focus on the most important factors.

Finally, it’s time to test the hypothesis. For example, if the main problem is low FPS, and the potential major cause is related to list rendering, we can think of some improvements in the area of images in the list items. We need to design a test that will help us accept or reject the hypothesis — it will probably be some kind of proof of concept. Next, we interpret the results and assess whether it improves our React Native app performance or not. Then, we make a final decision.

Cause and effect diagram example
Cause and effect diagram example

Improve

Now that we know our goal and how we want to achieve it, it’s time to actually improve your app performance. Before starting, it’s a good idea to hold another brainstorming session and identify potential solutions. Depending on the root cause, there might be a lot of them. Based on the last example with images on the list item, we can think about implementing proper image caching and reducing unnecessary renders.

After outlining the solutions, it’s time to pick the best one. Sometimes the solution that gives the best effects might be extremely costly, e.g., when it involves architectural changes. Once you implement the solution, don’t forget to test it properly!

Control

The last step in the DMAIC process is the control phase. We need to make sure that everything works well now. As you know by now, app performance will degrade if it is not under control. People tend to blame devices, technology, or even users for it, but it’s not that simple. So what do we need to do to keep the performance of our React Native app on a high level?

We need to make sure that we have a control plan. We can use some of our work from the previous phases to make it. We should point out focal points, some measurement characteristics, acceptable ranges for indicators, and testing frequency. Additionally, it is a good practice to write down some procedures and what to do if we spot issues.

Performance regression monitoring as a part of the development process

The most important aspect of the control phase is monitoring regressions. Until recently, it was quite difficult to do that effectively in React Native, but now we have plenty of options to improve our monitoring.

One way to keep track of the performance improvements we introduce in our apps is through real-time monitoring tools like Firebase Performance Monitoring or Sentry Performance Monitoring.

Another way to implement performance regression monitoring is through automated testing, which we’d like to explore in greater detail. Profiling, measuring, and running on various devices is a manual and time-consuming job that developers avoid doing. However, it gets too easy to unintentionally introduce performance regressions that would only get caught during QA, or worse, by your users. Thankfully, we can write automated performance regression tests in JavaScript for React and React Native with Reassure.

Reassure allows you to automate React Native app performance regression testing on CI or a local machine. Just like you write your integration and unit tests that automatically verify that your app is still working correctly, you can write performance tests that verify that your app is still performant. You can think about it as a React performance testing library. In fact, Reassure is designed to reuse as much of your React Native Testing Library tests and setup as possible as it’s designed by its maintainers and creators.

It works by measuring certain characteristics — render duration and render count — of the testing scenario you provide and comparing that to the stable version measured beforehand. It repeats the scenario multiple times to reduce the impact of random variations in render times caused by the runtime environment. Then it applies a statistical analysis to figure out whether the code changes are statistically significant or not. As a result, it generates a human-readable report summarizing the results and displays it on CI or as a comment to your pull request.

The simplest test you can write would look something like this:

import React from "react";
import { View } from "react-native";
import { measurePerformance } from "reassure";

const Component = () => {
return <View />;
};

test("mounts Component", async () => {
await measurePerformance(<Component />);
});

Testing rented times of Component during mounting.tsx hosted with ❤ by GitHub

This test will measure the render times of Component during mounting and the resulting sync effects. Let’s take a look at a more complex example, though. Here we have a component that has a counter and a slow list component:

import React from "react";
import { Pressable, Text, View } from "react-native";
import { SlowList } from "./SlowList";

const AsyncComponent = () => {
const [count, setCount] = React.useState(0);

const handlePress = () => {
setTimeout(() => setCount((c) => c + 1), 10);
};

return (
<View>
<Pressable accessibilityRole="button" onPress={handlePress}>
<Text>Action</Text>
</Pressable>

<Text>Count: {count}</Text>

<SlowList count={200} />
</View>
);
};

Component with a counter & slow list component.tsx hosted with ❤ by GitHub

And the performance test looks as follows:

import React from "react";
import { screen, fireEvent } from "@testing-library/react-native";
import { measurePerformance } from "reassure";
import { AsyncComponent } from "../AsyncComponent";
test("AsyncComponent", async () => {
const scenario = async () => {
const button = screen.getByText("Action");

fireEvent.press(button);
await screen.findByText("Count: 1");

fireEvent.press(button);
await screen.findByText("Count: 2");

fireEvent.press(button);
fireEvent.press(button);
fireEvent.press(button);
await screen.findByText("Count: 5");
};

await measurePerformance(<AsyncComponent />, { scenario });
});

Test for component with counter & slow list components.tsx hosted with ❤ by GitHub

When run through its CLI, Reassure will generate a performance comparison report. It’s important to note that to get a diff of measurements, we need to run it twice. The first time with a `— — baseline flag`, which collects the measurements under the `.reassure/ directory`.

performance tests

After running this command, we can start optimizing our code and see how it affects the performance of our component. Normally, we would keep the baseline measurement and wait for performance regressions to be caught and reported by Reassure. In this case, we’ll skip that step and jump straight into optimizing because we just noticed a nice possibility to do so. And since we have our baseline measurement for reference, we can actually verify our assumptions and whether the improvement was objective or only subjective.

The possibility we noticed is that the `<SlowList/>` component can be memoized, as it doesn’t depend on any external variables. We can leverage `useMemo` for that case:

const slowList = useMemo(() => <SlowList count={200} />, []);

Slowlist memoized.tsx hosted with ❤ by GitHub

Once we’re done, we can run Reassure a second time. Now without the ` — — baseline flag`.

performance comparison

Now that Reassure has two test runs to compare — the current and the baseline — it can prepare a performance comparison report. As you can notice, thanks to applying memoization to the `SlowList` component rendered by `AsyncComponent`, the render duration went from 78.4 ms to 26.3 ms, which is roughly a 66% performance improvement.

Test results are assigned to certain categories:

  • Significant Changes To Render Duration shows a test scenario where the change is statistically significant and should be looked into as it marks a potential performance loss/improvement.
  • Meaningless Changes To Render Duration shows test scenarios where the change is not statistically significant.
  • Changes To Render Count shows test scenarios where the render count did change.
  • Added Scenarios shows test scenarios that do not exist in the baseline measurements.
  • Removed Scenarios shows test scenarios that do not exist in the current measurements.

When connected with Danger JS, Reassure can output this report as a GitHub comment, which helps catch the regressions during code review.

performance comparison report

You can discover more use cases and examples in the docs.

DMAIC process and performance regression monitoring help make your React Native app consistently fast

When working on an app, regardless of its size, it’s essential to have a clear path for reaching our goals. The main benefit of using DMAIC methodology when optimizing React Native applications is a structured and direct approach. Without it, verifying what works (and why) may be difficult. Sometimes our experience and intuition are just enough. But that’s not always the case.

Making a process like this a part of React Native development allows us to focus on problem-solving and constantly increase productivity. Thanks to the DMAIC approach, performance optimization becomes a part of your normal development workflow. It brings your React Native app closer to being performant by default and allows spotting performance issues even before they hit your users.

No software is flawless. Bugs and performance issues will happen even if you’re the most experienced developer on the team. But we can take action to mitigate those risks by using automated tools like Sentry, Firebase, or Reassure. Use them in your project to enjoy the additional confidence they bring to your projects and the improved UX they deliver to your users.

If you’re looking for a tech partner who can improve the performance of your application in a well-thought-out process, contact us.

This article was originally published at callstack.com on February 8, 2023.

--

--