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

JSI - iOS implementation #541

Merged
merged 112 commits into from
May 26, 2020
Merged

JSI - iOS implementation #541

merged 112 commits into from
May 26, 2020

Conversation

radex
Copy link
Collaborator

@radex radex commented Oct 25, 2019

No description provided.

@radex radex self-assigned this Oct 25, 2019
facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Feb 20, 2020
Summary:
JSC does some sort of thread safety locking on every single JSC API call, which makes them ridiculously expensive for a large number of calls (such as when passing a large array over the RN bridge). It would be great if we could lock and unlock once for an entire sequence of JSC calls… but in the meantime, the less we call JSC the better.

![unknown](https://user-images.githubusercontent.com/183747/67624956-08bd6e00-f838-11e9-8f65-e077693f113d.png)

In my benchmark environment (Nozbe/WatermelonDB#541), the time spent in JSCRuntime::createValue went down from 1.07s to 0.69s by changing JSValueIsXXXX calls to a single JSValueGetType call.

The underlying implementation does the same thing: https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/API/JSValueRef.cpp#L58

## Changelog

[General] [Fixed] - Make JSCRuntime::createValue faster
Pull Request resolved: #27016

Reviewed By: RSNara

Differential Revision: D18769047

Pulled By: mhorowitz

fbshipit-source-id: 9d1ee28840303f7721e065c1b3c347e354cd7fef
osdnk pushed a commit to osdnk/react-native that referenced this pull request Mar 9, 2020
Summary:
JSC does some sort of thread safety locking on every single JSC API call, which makes them ridiculously expensive for a large number of calls (such as when passing a large array over the RN bridge). It would be great if we could lock and unlock once for an entire sequence of JSC calls… but in the meantime, the less we call JSC the better.

![unknown](https://user-images.githubusercontent.com/183747/67624956-08bd6e00-f838-11e9-8f65-e077693f113d.png)

In my benchmark environment (Nozbe/WatermelonDB#541), the time spent in JSCRuntime::createValue went down from 1.07s to 0.69s by changing JSValueIsXXXX calls to a single JSValueGetType call.

The underlying implementation does the same thing: https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/API/JSValueRef.cpp#L58

## Changelog

[General] [Fixed] - Make JSCRuntime::createValue faster
Pull Request resolved: facebook#27016

Reviewed By: RSNara

Differential Revision: D18769047

Pulled By: mhorowitz

fbshipit-source-id: 9d1ee28840303f7721e065c1b3c347e354cd7fef
@radex radex changed the base branch from jsi-part3 to jsi-part4 May 18, 2020 12:57
@radex
Copy link
Collaborator Author

radex commented May 18, 2020

‼️ Dependent branch(es)! ‼️

11 months after first toying with the idea (#426), the iOS implementation of the JSI adapter is DONE.

@kokusGr Sorry about the gargantuan PR. I've split it up into smaller chunks as much as I could… but for a long time I didn't know what final shape the implementation will take so it was hard for me to submit PRs over time.

Also, the Database.cpp implementation is big. In the ideal world, I would have split up the React Native-specific concerns from WatermelonDB-specific logic from SQLite-specific logic (like with the old adapter). However, this is performance-critical code and sadly, doing so is a major no-go. Transforming data between RN's representation and sqlite C representation is the biggest bottleneck, and an intermediate transformation into a common C++ STL representation would mean a huge performance (and peak memory use) hit. So sadly, we have to intertwine jsi, sqlite3, and watermelon-specific logic in the same place.

@kokusGr After you review this, give it to @jsamelak to take a look at the C++ code.

@radex radex requested review from kokusGr and jsamelak May 18, 2020 13:09
@radex radex assigned kokusGr and unassigned radex May 18, 2020
Copy link
Contributor

@kokusGr kokusGr left a comment

Choose a reason for hiding this comment

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

LGTM :) Have some questions and pointers (hehe), but I don't know c++ very well so couldn't be of much help.

native/ios/WatermelonDB/Database.cpp Show resolved Hide resolved

if (resultPrepare != SQLITE_OK) {
sqlite3_finalize(statement);
throw dbError("Failed to prepare query statement");
Copy link
Contributor

Choose a reason for hiding this comment

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

Yea, seems like a good idea to pass error code from sqlite3_finalize. Unless there are some implications of doing so 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If the most recent evaluation of statement S failed, then sqlite3_finalize(S) returns the appropriate error code or extended error code.

if I understand this correctly, finalize never fails - just returns, as a convenience the last state of running the statement - but we already check that.

or maybe I didn't understand your feedback?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry maybe I should write this under this comment // TODO: In serialized threading mode, those may be incorrect - probably smarter to pass result codes around? If sqlite3_finalize already returns the code we can pass it to dbError :) Unless it's different than code returned by sqlite3_extended_errcode.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ah. yes, two things:

  • currently we don't do multithreaded operations, so no need to worry about it. if we do, I think we'll have to revise all dbError calls, but i'll need to do more research
  • indeed what sqlite returns is different from sqlite3_extended_errcode… they return… well… non-extended error codes :D and you use the separate function to get a more specific reason for an error.

native/ios/WatermelonDB/Database.cpp Show resolved Hide resolved
native/ios/WatermelonDB/Database.cpp Show resolved Hide resolved
@kokusGr kokusGr assigned jsamelak and unassigned kokusGr May 20, 2020
return cachedRecords_.find(cacheKey) != cachedRecords_.end();
}
void Database::markAsCached(std::string cacheKey) {
// TODO: what about duplicates?
Copy link
Contributor

Choose a reason for hiding this comment

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

cachedRecords_ is of type unordered_set, and sets cannot contain duplicates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

noted


jsi::Array records(rt, 0);

for (size_t i = 0; true; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

is it guaranteed that stepResult eventually will be equal to SQLITE_DONE? Otherwise you could end with infinite loop

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yep - we get SQLITE_ROW zero or more (but not infinite) number of times, followed by SQLITE_DONE — or another status for error (which will break out of the loop by throwing).

auto &rt = getRt();
beginTransaction();
try {
// TODO: Maybe it's faster & easier to do it in one query?
Copy link
Contributor

Choose a reason for hiding this comment

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

You could gather IDs into list and perform delete from (...) where id IN (...IDs)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, I know, but i can't pass an array as a placeholder argument, so I'd have to concatenate strings which I wanted to avoid doing in c++. I'm gonna leave it like that for now, but i'll get back to deletion performance later and see if this matters

@jsamelak jsamelak assigned radex and unassigned jsamelak May 21, 2020
@radex radex assigned jsamelak and unassigned radex May 21, 2020
@jsamelak
Copy link
Contributor

@radex you want me to merge this PR?

@jsamelak jsamelak assigned radex and unassigned jsamelak May 21, 2020
@radex
Copy link
Collaborator Author

radex commented May 21, 2020

not yet, there's a parent PR waiting

Base automatically changed from jsi-part4 to master May 26, 2020 09:54
@radex radex merged commit e57a0ae into master May 26, 2020
@radex radex deleted the jsi-ios-poc branch May 26, 2020 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants