react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Bridge is 7x slower to send an array `x` versus `JSON.stringify(x)` to an Android native component

Open arvoelke opened this issue 4 years ago • 3 comments

Description

Sending an array x to an Android native component is incredibly slow. The overhead is roughly 25-26 ms just to send a single array of 16,384 integers.

However, if I send JSON.stringify(x) instead of x then it takes only 3-4 ms, even when including the JSON serialization in the timing! That is roughly 7x faster.

This does not make much sense as the serialization should be slowing things down, and the volume of data being transmitted across the bridge should also be significantly larger due to it being encoded as an ASCII string.

Even if there is a good reason for this performance penalty, ideally the framework should be doing whatever serialization and deserialization across the bridge is most efficient. That is, if it is more efficient to transmit strings from JS to Java under the hood, then the react-native bridge should take care of that for the user, rather than requiring the user to discover this and perform this counterintuitive optimization themselves. Perhaps the user could hint to the framework what kind of protocol to use under the hood depending on the type of array. Alternatively, this might point to some performance regression in how arrays are currently being handled by the bridge and some aspect of the current implementation that could be improved.

Note I am using Xiaomi 8 running Android 10, with JS dev mode disabled and no debugging nor console logging.

React Native version:

System:
    OS: Linux 5.4 Ubuntu 18.04.3 LTS (Bionic Beaver)
    CPU: (24) x64 AMD Ryzen 9 3900X 12-Core Processor
    Memory: 47.00 GB / 62.82 GB
    Shell: 4.4.20 - /bin/bash
  Binaries:
    Node: 14.17.1 - /usr/bin/node
    Yarn: 1.22.10 - /usr/bin/yarn
    npm: 7.17.0 - /usr/bin/npm
    Watchman: Not Found
  SDKs:
    Android SDK: Not Found
  IDEs:
    Android Studio: Not Found
  Languages:
    Java: 1.8.0_292 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.1 => 17.0.1 
    react-native: 0.64.2 => 0.64.2 
  npmGlobalPackages:
    *react-native*: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Make a hello world App.js containing the code:
...
  componentDidMount() {
      this.profileNative();
  }

  makeRandomSignal() {
    var sample = [];
    for (var i = 0; i < 16384; i++) {
      sample[i] = 127;
    }
    return sample;
  }

  profileNative() {
    const x = this.makeRandomSignal();
    var tBegin = global.performance.now();
    MyNativeComponent.go(x, r => {
      var output = global.performance.now() - tBegin;
      // Display output value in app (e.g., with this.setState)
      this.profileNative();
    });
  }
...
  1. Make an Android native component (MyNativeComponent) containing the go implementation:
...
    @ReactMethod
    public void go(ReadableArray x, Callback callback) {
        callback.invoke("");
    }
...

Running this on my phone reports roughly 25-26 ms in overhead.

  1. Now simply change go(x, ...) to go(JSON.stringify(x), ...) in App.js and change public void go(ReadableArray x, ...) to public void go(String x, ...). As in:
...
    MyNativeComponent.go(JSON.stringify(x), r => {
...
...
    @ReactMethod
    public void go(String x, Callback callback) {
        callback.invoke("");
    }
...

And the reported timing drops to roughly 3-4 ms.

Expected Results

I expected the additional volume of data and JSON serialization to result in more overhead -- not 7x less. There should be a way to send this array in 3 ms without jumping through hoops to serialize it as a string.

Snack, code example, screenshot, or link to a repository:

See above.

I believe this issue is closely related to #10504. That issue was closed without any resolution or improvement.

I could provide a stand-alone regression test if I had some starting point to work off, but I believe the above is enough to show that there is a serious problem that should not be expected, and the steps that can be taken to reproduce this problem.

arvoelke avatar Jun 24 '21 16:06 arvoelke

I concur that passing byte array in ReadableArray format over RN bridge is quite slow. My scenario is to surface up protobuf bytes from native layer up to JS layer, I ended up using base64 to encode / decode the bytes and pass the bridge in string format, it's much better.

With 2,000 bytes of payload: ReadableArray: 3 ms encoding + 5 ms passing through String (base64): 0.3 ms encoding + 2 ms passing through

With 20,000 bytes of payload: ReadableArray: 26 ms encoding + 18 ms passing through String (base64): 3 ms encoding + 2 ms passing through

raymondxie avatar Mar 09 '23 22:03 raymondxie

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] avatar Feb 18 '24 05:02 github-actions[bot]

This is still an issue as far as I know. Has not yet been acknowledged.

arvoelke avatar Feb 18 '24 11:02 arvoelke

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

react-native-bot avatar Aug 17 '24 05:08 react-native-bot

This is still an issue as far as I know. Has not yet been acknowledged.

arvoelke avatar Aug 18 '24 00:08 arvoelke