Deep dive into optimizing React Native JSI native module performance through data representation choices. Benchmarks show returning arrays of objects is up to 30x slower than using ArrayBuffer/MutableBuffer for numeric data crossing the C++/JS boundary. Passing type parameters as numeric indices with lookup tables beats string comparisons, and stack-allocated char buffers or std::to_chars outperform std::format and std::to_string for hot-path string building. Concrete millisecond benchmarks for 100,000 calls accompany each technique.
Table of contents
Data shape: from objects to raw memoryAPI shape: strings vs numbersString building: convenience vs performanceNumber to string conversionFinal thoughtsQuestions this post answers
Why is returning an array of objects from a JSI native function so slow compared to ArrayBuffer?
Each object requires a separate JS heap allocation and every setProperty call crosses the JSI boundary, adding GC pressure and per-call overhead. In a benchmark of 100,000 calls returning 50 points, an array of objects took 1036.50ms versus 34.81ms using ArrayBuffer with a MutableBuffer subclass, a roughly 30x difference, because ArrayBuffer shares native memory directly with JS with zero per-element calls. Developers tuning native module boundaries can track C++ and JSI performance patterns like this on daily.dev.
Is std::to_chars faster than std::to_string for converting numbers to strings in a hot loop?
Yes, std::to_chars is about 1.7x faster than std::to_string in a benchmark of 100,000 calls (9.75ms vs 16.27ms), because it writes directly into a provided buffer without allocating temporary std::string objects or depending on locale formatting. It suits hot paths with simple, bounded output like IDs, counters, or timestamps, while std::to_string remains the better default for regular code. Anyone optimizing C++ hot paths can follow comparisons like this on daily.dev when choosing string conversion approaches.
Should I pass a string or a number when a JSI function needs to select a type from JavaScript?
Pass a numeric index rather than a string. Comparing a JS string requires asString(rt).utf8(rt), which allocates and copies across the JSI boundary, then runs multiple string comparisons, taking 12.88ms per 100,000 calls versus 9.13ms for a switch on an int and 8.67ms for a lookup table indexed by that int, the fastest and cleanest option. Developers designing native module APIs can compare JSI parameter patterns like this on daily.dev.