Closed michalchudziak closed 1 year ago
Hi @michalchudziak, thanks for reporting this.
Could you describe the memory consumption pattern in a little more detail? Does Hermes remain consistently higher than JSC, or does the difference vary significantly over time? How does the 20-40MB compare to the overall memory consumption?
Note that comparing specific allocations across the engines generally doesn't work well, since the implementations are completely different. Are you able to share a more complete memory profile? Including anonymous mappings as well will be helpful, since most of Hermes' memory is typically backed by them.
That said, looking at the top allocations you've highlighted here.
If the first category is unique to Hermes, it's likely that it is coming from Intl
. Does you application make heavy use of Intl
?
For the two large malloc
allocations in Hermes, I'd typically look at large ArrayBuffer
s or strings.
Using some of the memory profilers in Chrome DevTools while attached to the RN app may also help you pinpoint where the two large allocations are coming from in JS.
@michalchudziak Can you also confirm that you are experiencing this with the release version of the app?
Hey @neildhar @tmikov thank you for the swift response.
Does Hermes remain consistently higher than JSC, or does the difference vary significantly over time?
The difference is not constant, but it's consistently higher than JSC. 20-40 is an average from tested scenarios (I ran it 10 times per scenario to reduce variance). Sometimes it exceeds that value, but I haven't spotted any leaks.
How does the 20-40MB compare to the overall memory consumption?
Note that comparing specific allocations across the engines generally doesn't work well, since the implementations are completely different. Are you able to share a more complete memory profile? Including anonymous mappings as well will be helpful, since most of Hermes' memory is typically backed by them.
I can share some logs on Discord, I'll send you a DM.
Does you application make heavy use of Intl?
We use a lot of translations and support many languages, but regarding Intl
APIs, we use them only in several places.
Can you also confirm that you are experiencing this with the release version of the app?
Yes, I've been profiling the release version of the app
Here is a comparison with an adapted test from https://github.com/ArashPartow/exprtk (see attached):
node-hermes --version
LLVM (http://llvm.org/):
LLVH version 8.0.0svn
Optimized build
/usr/bin/time node-hermes exprtk_functional_test.txt.js
7445 7299
0.62user 0.12system 0:00.74elapsed 99%CPU (0avgtext+0avgdata 202504maxresident)k
0inputs+0outputs (0major+55426minor)pagefaults 0swaps
/usr/bin/time qjs exprtk_functional_test.txt.js
7445 7299
0.07user 0.00system 0:00.08elapsed 98%CPU (0avgtext+0avgdata 6176maxresident)k
0inputs+0outputs (0major+1716minor)pagefaults 0swaps
node -v
v16.17.1
/usr/bin/time node exprtk_functional_test.txt.js
7445 7299
0.07user 0.02system 0:00.09elapsed 102%CPU (0avgtext+0avgdata 46368maxresident)k
0inputs+0outputs (0major+6511minor)pagefaults 0swaps
Hey, I have the same issue in my project, I upgraded from React Native version 0.69.6 to 0.70.5 some time ago, since then I noticed that the app launch is slower than before (the splash screen is longer visible). To find the cause of this, I used Xcode's allocations profile instrument. This is where I noticed that the app loads ~50 MB extra on launch, but only when Hermes is enabled. I made a small memory comparison between the versions, each with Hermes and JSC.
The results are (Release Configuration and iPhone 14 Pro):
@mingodad node-hermes
is considered experimental, and Hermes is generally not optimised for running from source. Running with hermes
(after setting console.log
to print
) and precompiling the JS first makes the comparison much more favourable. (you can precompile with hermes -emit-binary -out foo.hbc foo.js
and then run it with hermes foo.hbc
)
@mirzalikic can you verify that the built iOS app contains bytecode and not JS as well? You can look at the bundle by unzipping the IPA.
@neildhar just checked the build, the main.jsbundle contains minified JS code instead of bytecode. Any idea how to fix this?
@mirzalikic that would certainly explain the memory regression. I don't know off-hand what might cause this, since RN does the bundling and compilation to bytecode.
@michalchudziak has previously mentioned that he had success patching in https://github.com/facebook/react-native/commit/03de19745eec9a0d4d1075bac48639ecf1d41352, so that may be worth trying.
@neildhar thanks, this patch fixed the problem. I saw that React-Native 0.71.0 was released yesterday with this fix, so the update should solve the issue as well.
@mirzalikic thanks for the update. To confirm, are you now observing better memory consumption with Hermes?
yes i can confirm the issue is gone now, https://github.com/facebook/react-native/commit/03de19745eec9a0d4d1075bac48639ecf1d41352 this was the fix and its included in the latest React Native version
@mirzalikic are you sure that the memory consumption issue is gone? It seems that the fix you've linked is addressing the bytecode issue.
@michalchudziak yes memory consumption is great now, the bytecode issue was causing the high memory consumption.
the main.jsbundle only contained minified js code instead of compiled code.
Closing, since the issue has been successfully resolved.
@neildhar @tmikov Upon reviewing the profiles for version 0.71, it appears the previously identified issue persists, notably in the initial allocation pertaining to NSData mutableCopyWithZone. However, the other significant allocations associated with Hermes do not seem to be present.
For further context, I compared using a newly generated project through npx react-native init TestHermes
(0.71.8), observing it with Hermes both enabled and disabled. The analysis showed a noticeable difference in the total memory allocated, approximately ~1.17 MiB
, to the object under review.
It seems that it's a reference to JSBigString
allocated here https://github.com/facebook/react-native/blob/e25c6632a2360acfb63ceb62258ee1ec18452018/packages/react-native/ReactCommon/cxxreact/Instance.cpp#L106
Do you have any context on that? Is this a conscious allocation?
@michalchudziak the code you linked is not part of Hermes, so I am not sure what is happening there. Looks like JSBigString contains the bytecode bundle? Ideally it should be memory-mapped, but Hermes doesn't control how that is allocated.
As far as I can tell, JSBigString is an abstraction on top of mmap(), so I don't know why it is showed as malloc or what it has to do with NSData.
Bug Description
Hermes version: 0.12.0 React Native version (if any): 0.70.6 OS version (if any): iOS 16 Platform (most likely one of arm64-v8a, armeabi-v7a, x86, x86_64): iOS
Steps To Reproduce
In the app I'm currently working on, the memory consumption on iOS is significantly increased compared to JSC. The difference between using hermes and JSC differs from 20 MB to 40MB. I did memory allocation profiling using Xcode Instruments (release app on iPhone 12 Pro), and found following object allocations that are not present in the JSC builds. Unfortunately I can't provide the minimal repro project, but I'm curious wether anyone recognizes these objects.
The Expected Behavior
Memory consumption to be lower or equal to JSC.