sentry-react-native
sentry-react-native copied to clipboard
Use CPP Base64 Encoder for better performance (>10x)
Description
Hermes offers builtin btoa, using it or vendoring react-native-quick-base64, will bring 10x+ improvement in the event processing time.
Things to consider
- Add
carrier.base64Polyfillto clearly separate the event processing logic and the use encoders (same as for utf8) - ~~Use ~~
~~String.fromCharCode~~~~ + ~~~~btoa~~~~ both natively supported by Hermes~~- ~Hermes Team comment on speed of the builtins https://github.com/facebook/hermes/discussions/1393~
- ~Hermes Base64 source code https://github.com/facebook/hermes/blob/0760c58acf668f58eb909f7b8efa2b7fc2ff10d1/lib/VM/JSLib/Base64.cpp#L20~
- Won't for large arrays (profiling, attachments...)
- (Alternative) vendor JSI lib like react-native-quick-base64 to use
base64StringFromByteArray - Solution could be also try to avoid the base64 encoding of the envelopes (maybe pass it as an object to the native layers, or as an array (note that we have ran into issue in the past with the array size (link TBA)))
There is also function utf8ToBytes that is very slow. This function could possibly use native TextEncoder which should speed it up significantly. Here is implementation I used (10x - 15x faster than JS implementation):
/**
* Converts a UTF-8 string to an array of bytes using native TextEncoder.
* This function is intended as a more performant alternative to the
* utf8ToBytes function found in some Buffer polyfills.
*
* @param {string} string The string to convert.
* @param {number} [unitsInput] Optional. Similar to the 'units' in the polyfill's
* utf8ToBytes, this aims to limit the number of bytes.
* If 0, undefined, null, or NaN, it's treated as Infinity
* (no limit), mimicking the polyfill's `|| Infinity` behavior.
* Otherwise, the output byte array will be truncated to this length.
* @returns {number[]} An array of numbers, where each number represents a byte.
*/
export function utf8ToBytes(string, unitsInput) {
let units;
// Mimic the polyfill's `units = units || Infinity` behavior for common falsy values
// that would lead to `Infinity` in the original.
if (unitsInput === undefined ||
unitsInput === null ||
unitsInput === 0 || // Original `|| Infinity` makes 0 effectively Infinity
(typeof unitsInput === 'number' && Number.isNaN(unitsInput))) {
units = Infinity;
} else {
units = Number(unitsInput); // Coerce to number (e.g., if it was a string "10")
if (Number.isNaN(units)) { // If coercion results in NaN (e.g., from "abc")
units = Infinity;
}
}
const encoder = new TextEncoder(); // UTF-8 by default
const uint8Array = encoder.encode(string);
let finalUint8Array;
if (units < 0) {
// If units was specified as a negative number not caught by the Infinity conditions.
// The polyfill would also likely break immediately or produce no bytes.
finalUint8Array = new Uint8Array(0);
} else if (units !== Infinity && uint8Array.length > units) {
// Truncate if the encoded length is greater than the specified units.
// Ensure units is not negative for slice, though above logic should handle most.
finalUint8Array = uint8Array.slice(0, Math.max(0, units));
} else {
// No truncation needed or units is Infinity
finalUint8Array = uint8Array;
}
return finalUint8Array;
}
Totally agree. utf8ToBytes is too slow