Performance Tracking in React Native

Performance Tracking in React Native

  • 634

Tracking the Performance of Complex Components in React-Native

The react-native community has come up with good advice and tools to master the performance of react-native apps, such as learning about frame rates and how to use systrace, understanding over-rendering (also here), or using the component within react-native.

Sometimes however, you want to first have a quick first look at the performance behaviour of some task. I stumbled on such a situation when I wanted to know how AsyncStorage (now under community tutoring) behaves on large payloads. Without much ado, these are the results for writing and then reading back a large array.

This is image title

Don’t pay attention to absolute timings, they will vary depending on the hardware of the phone. These numbers were collected on an iOs simulator running on a Mac —which will offer plenty of memory and disk storage compared to the HW actually available on a given phone.

The good news? The behaviour is linear —which means there are no hidden overheads in the native implementation (this confirms on both iOS and android).

The better news? I run the app on an iPhone X and on a Mi A2 Lite and you can indeed expect AsyncStorage to store an array of 100,000 elements in less than 100ms. Chakka.

Measuring performance

In order to measure the time to completion of an isolated code part, I have used the javascript Date().now() function before—alas, this function is not available in the JS engine included with react-native.

We used a simple timer instead.

interval = null;
startTime = 0;
state = { perf: 0 }
...
trackPerf = (run) => {
  if(run) {
    this.interval && clearInterval(this.interval);
    this.setState({ perf: 0 });
    this.startTime = new Date();
    this.interval = setInterval(() => {
      const elapsed = new Date() - this.startTime;
      this.setState({ perf: elapsed, });
    }, 100);
  } else {
    this.interval && clearInterval(this.interval);
  }
}

which basically will tick every 100 milliseconds, add the actually elapsed time on every tick and keep ticking until it is instructed to stop. Like this

this.trackPerf(true);
await Store.set(this.ARRAY, storedArray, this.setError);
const newItem = await Store.get(this.ARRAY, this.setError);
this.trackPerf(false);

Now, these measurements will not be exact. The timer adds payload to the JS thread. But, they will be perfectly OK for a qualitative analysis.

Displaying the results

For this test —since I had an isolated task in mind— I quickly wrote a test app (thanks react-native for making that soooo easy to do!).

The render() method looks like basically this

const { storedArray, errorMsg, perf } = this.state;
return (
  <View style={styles.container}>
...
    <Text style={styles.results}>
      <Text>Array length: </Text><Text>{storedArray.length}</Text>
    </Text>
    <Text style={styles.results}>
      <Text>W+R in </Text><Text>{perf}</Text><Text>ms</Text>
    </Text>
...
    <Text style={styles.footer}>
      <Text>{errorMsg}</Text>
    </Text>
  </View>
);

…and updates the values displayed via the components state.

This method is great for simple cases. In more complex cases you may have to refactor trackPerf() to become a component itself with its own state. This could allow you to insert several meter points in the app. I have also used in the past a trick: wrap an innocuous display element in a <TouchableOpacity> component whose onPress callback triggers an Alert.alert() showing your perf metering. Something like this

showPerf = () => {
  const { perf, error } = this.state;
  const info = `Elapsed: ${perf}, Error ${error}`;
  setTimeout(() => {
    Alert.alert('Info', info, [
        { text: 'ok' }, {},
      ]);
  }, 300);
}
...
render(){
...
  <TouchableOpacity onPress={this.showPerf}>
    <Image source={require('../images/logo.png')} />
  </TouchableOpacity>
...
}

In case you are interested, the full App code is available at GitHub.

Comments and claps are welcome.