June 2022
Here is the latest information dump my findings with React Native’s JSI.
C++ is the fastest but Swift is fast, Java ByteCode is also fast. Using JSI/TurboModules won’t necessarily make your module faster. The JSI is a communication layer, therefore it excels in situations where you transfer a lot of data between native and JavaScript.
If you need to pass a few bytes once (or a few times), the old APIs are easier to work with and the performance penalty might be small enough that you might be better off skipping the JSI. Reading a small piece of data from AsyncStorage takes 5ms on release mode, so this extrapolates to any package that does a lot of calculation on the native side and then only returns a small chunk of data to JavaScript.
Here is a comparison, reading a small string (“hello”), one time (release mode -O2 optimized):
Framework | Time |
---|---|
MMKV | > 0ms |
Quick SQLite | > 0ms |
WatermelonDB | > 8ms |
Async Storage | > 5ms |
Artificial benchmarks testing reading the same value or calculating something thousands of times are… disingenuous.
Most frameworks have caches implemented to them, reading anything more than once results in testing if the cache is there. Other important details cannot be ignored: MMKV is a key/value storage, whereas Quick SQLite and WatermelonDB are relational databases on top of SQLite, comparing them is comparing apples to oranges.
Calculating data thousands of times will also mix JSI performance with C++/Swift/Kotlin performance, hard to tell when one ends and the other begins.
IMO it is better to test transferring a large amount of data. However, it is hard to find a test that actually makes sense, because details can affect the performance. Returning one large string will only mem-copy (Strings on your native code are not returned directly to JavaScript, but the memory needs to be copied to JSI Strings) once, whereas returning a lot of strings, will have allocate memory multiple times.
I can share some anecdotic experience of some people using Quick SQLite. Takuya experienced 2x to 5x speed boost when switching from the old bridge SQLite driver, user @sallar experienced 2x - 2.5x speed improvement, query time reduced from 600ms to ~250ms, with large SQLite queries. The larger your SQL results the better will Quick SQLite perform for you.
Just to be clear JSI does cuts the overhead of communication, transferring a few bytes once is just not the best use-case. It will also excel in cases where you transfer a small chunk of data but you need to do it very often, e.g. reanimated.
Among the questions that get repeated over and over is “how can I use my favorite language?”. I even made a video about it, go watch it.
In the video I made a mistake, I made it sound like there is no possibility to use any other language, which is not technically correct. So here it is explained in a list so hopefully it will be clearer for everyone:
You can use w/e you want, it’s just that you will have to manually modify and sync your function signatures. And to be clear this is partly a limitation of JSI and in some cases compatibility between languages (e.g. Swift and C++ interoperability).
Don’t drink this cool-aid, all frameworks abstract the heavy-lifting for you. Even if you would write your app in native there would be occasions where you will have to deal with some complexity. If not on the language level, it would be on the API level, on some integration, on some framework model. etc. I have written a fair amount of native code at this point and I it doesn’t get any easier.
I have already seen tweets from flutter devs complaining they are writing dart/swift/kotlin/c++ code at the same time. If you are interested in this topic you are going deep into the inner working of the frameworks, this is usually what it takes to build software at this level.
As far as I can see here are the options: