Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function names to module factories in DEV #1130

Closed
wants to merge 3 commits into from

Conversation

gaearon
Copy link
Contributor

@gaearon gaearon commented Nov 2, 2023

This adds path-based function names to the generated module factories in development.

__d(function src_some_folder_path_MyComponent_js__module_factory__() {
  // ...
})

They're only emitted when minify is false and dev is true.

Motivation

Profiling module initialization is a common need. The current set of tooling React Native provides for it is arguably abysmal.

The primary tool suggested by React Native documentation is Systrace which literally does not exist anymore. "Profiling with Hermes" suggests using sampling profiler which seems to barely work — I wasn't able to capture any information with it.

If you try to use Hermes Chrome DevTools integration and press "Record" in the Performance tab, it hangs.

I almost gave up at that point, but @brentvatne kindly suggested that there is a workaround — I need to Shift+Cmd+P to get a different "JS Profiler" Chrome DevTools tab to show up, and that one actually works.

Even that approach is a little bit broken but it's the closest to what I want.

Unfortunately, even that tool does not let me see which modules I'm spending time initializing:

Screenshot 2023-11-02 at 17 15 05

As you can see, module factories have blank function names.

This is not the case with web-based tooling. For example, webpack emits module factories as a dictionary like { "./src/some/module.js": function() {}, ... } so the stacks are useful. Since we don't want to change the format entirely, and configuring fn.name dynamically at runtime does not make it show up in the trace, I opted for a minimal possible change — to actually emit the module names in development.

As a result, I can see where I'm spending time during module initialization:

Screenshot 2023-11-02 at 17 24 47

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 2, 2023
@codecov-commenter
Copy link

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (6151e7e) 83.09% compared to head (eaf9dd1) 83.10%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1130   +/-   ##
=======================================
  Coverage   83.09%   83.10%           
=======================================
  Files         206      206           
  Lines       10547    10552    +5     
  Branches     2617     2619    +2     
=======================================
+ Hits         8764     8769    +5     
  Misses       1783     1783           
Files Coverage Δ
packages/metro-transform-worker/src/index.js 87.19% <100.00%> (+0.15%) ⬆️
...ges/metro/src/ModuleGraph/worker/JsFileWrapping.js 100.00% <100.00%> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@facebook-github-bot
Copy link
Contributor

@robhogan has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

gaearon added a commit to bluesky-social/social-app that referenced this pull request Nov 2, 2023
gaearon added a commit to bluesky-social/social-app that referenced this pull request Nov 2, 2023
gaearon added a commit to bluesky-social/social-app that referenced this pull request Nov 2, 2023
@@ -382,21 +387,21 @@ async function transformJS(
if (config.unstable_disableModuleWrapping === true) {
wrappedAst = ast;
} else {
let moduleFactoryName;
if (options.dev && !minify) {
moduleFactoryName = file.filename;
Copy link
Contributor

@motiz88 motiz88 Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern here (other than the obvious: we should improve the profiling & debugging tools so that this isn't needed 😅) is around the portability and cache consistency of using this filename property.

I'm not sure off the top of my head whether this is (1) an absolute or relative path, (2) a platform-dependent or normalised path. Could you confirm?

  • If the path is absolute, then transformer cache artifacts will be sensitive to the project's root directory, which is incorrect. (Cache artifacts should be compatible across machines, including e.g. checkouts at different base locations)
  • If the path is absolute and platform-dependent, then it would begin with C:\foo on Windows but /foo on POSIX, which again makes the cache non-portable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a relative path in my testing — see the screenshot (e.g. node_modules/blabla/...).

I'm not sure if it's platform-dependent or not but we replace all non-valid JS ident characters with _ so a path with / and \ would result in the same identifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we already emit a path into the DEV artifacts here (the module's "verbose name") but it happens at a different stage so I'm not entirely sure if they're constructed in the same way or come from different sources.

@facebook-github-bot
Copy link
Contributor

@robhogan merged this pull request in 2f62858.

@facebook-github-bot
Copy link
Contributor

This pull request has been reverted by 92340b3.

@robhogan
Copy link
Contributor

robhogan commented Nov 8, 2023

Urgh - this caused native crashes across multiple apps in debug builds post-land, still connecting the dots but there are runtime mem allocation failures coming from Hermes - first few stack frames, FYI:

SIGABRT:libxplat_hermes_API_HermesAPIAndroid.so:./xplat/hermes/external/llvh/include/llvh/Support/MemAlloc.h:llvh::SmallVectorBase::grow_pod(void*, unsigned int, unsigned int)

hermes::irgen::ESTreeIRGen::serializeScope(hermes::ScopeDesc*, bool)
hermes::irgen::ESTreeIRGen::setupLazyScope(hermes::ESTree::FunctionLikeNode*, hermes::Function*, hermes::ESTree::BlockStatementNode*)
hermes::irgen::ESTreeIRGen::genES5Function(hermes::Identifier, hermes::Variable*, hermes::ESTree::FunctionLikeNode*, bool)

@gaearon
Copy link
Contributor Author

gaearon commented Nov 17, 2023

Is there any chance that some (relative) paths are still incredibly long, or that Hermes has a limit on function names that isn't present in other JS engines? I was thinking of adding .slice(0, 50) or something.

@robhogan
Copy link
Contributor

robhogan commented Nov 17, 2023

Is there any chance that some (relative) paths are still incredibly long, or that Hermes has a limit on function names that isn't present in other JS engines? I was thinking of adding .slice(0, 50) or something.

String length is a factor, but probably not the determining one. We saw total RAM on Facebook Android (dev builds) go from 1.6GB to 3.8GB with this change, so way more than just the sum of those strings, even though the paths are pretty long.

@tmikov has been working on it and identified a quadratic growth situation where scopes were repeatedly re-serialised during lazy evaluation, but the fix for that didn't seem to be enough on its own.

Worst case, we could enable this behind a serializer config flag in Metro and document the memory use, but the Hermes folks were actively looking into it last I heard so they may want a bit more time.

@tmikov
Copy link

tmikov commented Nov 18, 2023

There is no limit, Hermes is just not optimized for running from source - this problem doesn't exist when running from bytecode. Running from source with lazy compilation is, in a sense, contrary to Hermes's mission of being an optimizing AOT compiler. We basically bolted lazy compilation on top of the AOT compiler and had to use a lot of hacks, because it simply wasn't designed for that. It is a miracle it has worked as well as it has...

We will support this properly in Static Hermes (at tremendous complexity cost), but for now we are trying to figure out exactly what is consuming the memory in "regular" Hermes. There is something quadratic. (We already fixed one quadratic case, but sadly it made no difference.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged Reverted
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants