Permalink
Browse files

Use JSStringCreateWithUTF8CString and skip NSString decoding when loa…

…ding the bundle

Summary: public

Benchmarking our startup path has shown we spend a lot of time decoding strings (iPhone 4S / iPhone 5):

* reading a 2MB JS bundle: 35ms / 15ms
* decoding is to an `NSString`: 186ms / 78ms
* transforming that to a `JSString`: 29ms / 10ms

Instead of going through an `NSString` transformation, we generate a null-terminated bundle (0.1ms / 0.05ms to copy the data) and use `JSStringCreateWithUTF8CString` (121ms / 53ms) to generate the string. That makes decoding 70% faster.

Reviewed By: javache

Differential Revision: D2541140

fb-gh-sync-id: 09a016b8edfd46a9b62682c76705564d2024e75e
  • Loading branch information...
ndfred authored and facebook-github-bot-3 committed Oct 16, 2015
1 parent 8e2ec64 commit 4a3857ef1dc073f4a58274b77e7f775ca81b39dd
@@ -40,7 +40,7 @@ - (void)setUp
- (void)testNativeLoggingHookExceptionBehavior
{
dispatch_semaphore_t doneSem = dispatch_semaphore_create(0);
[_executor executeApplicationScript:@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);"
[_executor executeApplicationScript:[@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);" dataUsingEncoding:NSUTF8StringEncoding]
sourceURL:[NSURL URLWithString:@"file://"]
onComplete:^(__unused id error){
dispatch_semaphore_signal(doneSem);
@@ -128,7 +128,7 @@ function require(module) { \
} \
";
[_executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(__unused NSError *error) {
[_executor executeApplicationScript:[script dataUsingEncoding:NSUTF8StringEncoding] sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(__unused NSError *error) {
NSMutableArray *params = [NSMutableArray new];
id data = @1;
for (int i = 0; i < 4; i++) {
@@ -148,7 +148,7 @@ - (void)sendMessage:(NSDictionary *)message waitForReply:(RCTWSMessageCallback)c
});
}
- (void)executeApplicationScript:(NSString *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
- (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
NSDictionary *message = @{
@"method": @"executeApplicationScript",
@@ -116,8 +116,8 @@ - (void)start
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSString *sourceCode;
[self loadSource:^(NSError *error, NSString *source) {
__block NSData *sourceCode;
[self loadSource:^(NSError *error, NSData *source) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
@@ -184,7 +184,7 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
RCTPerformanceLoggerStart(RCTPLScriptDownload);
int cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil);
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) {
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source) {
RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil);
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
@@ -195,12 +195,13 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
// Force JS __DEV__ value to match RCT_DEBUG
if (shouldOverrideDev) {
NSRange range = [source rangeOfString:@"__DEV__="];
NSString *sourceString = [[NSString alloc] initWithData:source encoding:NSUTF8StringEncoding];
NSRange range = [sourceString rangeOfString:@"__DEV__="];
RCTAssert(range.location != NSNotFound, @"It looks like the implementation"
"of __DEV__ has changed. Update -[RCTBatchedBridge loadSource:].");
NSRange valueRange = {range.location + range.length, 2};
if ([[source substringWithRange:valueRange] isEqualToString:@"!1"]) {
source = [source stringByReplacingCharactersInRange:valueRange withString:@" 1"];
if ([[sourceString substringWithRange:valueRange] isEqualToString:@"!1"]) {
source = [[sourceString stringByReplacingCharactersInRange:valueRange withString:@" 1"] dataUsingEncoding:NSUTF8StringEncoding];
}
}
@@ -355,15 +356,15 @@ - (void)injectJSONConfiguration:(NSString *)configJSON
callback:onComplete];
}
- (void)executeSourceCode:(NSString *)sourceCode
- (void)executeSourceCode:(NSData *)sourceCode
{
if (!self.valid || !_javaScriptExecutor) {
return;
}
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = self.bundleURL;
sourceCodeModule.scriptText = sourceCode;
sourceCodeModule.scriptData = sourceCode;
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
if (!self.isValid) {
@@ -585,7 +586,7 @@ - (void)_immediatelyCallTimer:(NSNumber *)timer
}
}
- (void)enqueueApplicationScript:(NSString *)script
- (void)enqueueApplicationScript:(NSData *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
@@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
typedef void (^RCTSourceLoadBlock)(NSError *error, NSString *source);
typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source);
@class RCTBridge;
@@ -46,7 +46,7 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
/**
* Runs an application script, and notifies of the script load being complete via `onComplete`.
*/
- (void)executeApplicationScript:(NSString *)script
- (void)executeApplicationScript:(NSData *)script
sourceURL:(NSURL *)sourceURL
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
@@ -36,8 +36,10 @@ + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComp
NSString *filePath = scriptURL.path;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSString *rawText = [NSString stringWithContentsOfFile:filePath usedEncoding:NULL error:&error];
onComplete(error, rawText);
NSData *source = [NSData dataWithContentsOfFile:filePath
options:NSDataReadingMappedIfSafe
error:&error];
onComplete(error, source);
});
return;
}
@@ -71,10 +73,10 @@ + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComp
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
}
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
// Handle HTTP errors
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
NSDictionary *userInfo;
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
@@ -101,7 +103,7 @@ + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComp
onComplete(error, nil);
return;
}
onComplete(nil, rawText);
onComplete(nil, data);
}];
[task resume];
@@ -494,7 +494,7 @@ - (void)executeJSCall:(NSString *)name
}), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))];
}
- (void)executeApplicationScript:(NSString *)script
- (void)executeApplicationScript:(NSData *)script
sourceURL:(NSURL *)sourceURL
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
@@ -508,9 +508,15 @@ - (void)executeApplicationScript:(NSString *)script
return;
}
// JSStringCreateWithUTF8CString expects a null terminated C string
NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1];
[nullTerminatedScript appendData:script];
[nullTerminatedScript appendBytes:"" length:1];
RCTPerformanceLoggerStart(RCTPLScriptExecution);
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef execJSString = JSStringCreateWithUTF8CString(nullTerminatedScript.bytes);
JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString);
JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError);
JSStringRelease(jsURL);
@@ -130,7 +130,7 @@ - (void)executeJSCall:(NSString *)name
* debugger. So we have to use this (essentially) async API - and register
* ourselves as the webview delegate to be notified when load is complete.
*/
- (void)executeApplicationScript:(NSString *)script
- (void)executeApplicationScript:(NSData *)script
sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
@@ -142,6 +142,7 @@ - (void)executeApplicationScript:(NSString *)script
}
RCTAssert(onComplete != nil, @"");
NSString *scriptString = [[NSString alloc] initWithData:script encoding:NSUTF8StringEncoding];
__weak RCTWebViewExecutor *weakSelf = self;
_onApplicationScriptLoaded = ^(NSError *error){
RCTWebViewExecutor *strongSelf = weakSelf;
@@ -163,23 +164,23 @@ - (void)executeApplicationScript:(NSString *)script
}];
[_objectsToInject removeAllObjects];
[scriptWithInjections appendString:@"/* END NATIVELY INJECTED OBJECTS */\n"];
[scriptWithInjections appendString:script];
script = scriptWithInjections;
[scriptWithInjections appendString:scriptString];
scriptString = scriptWithInjections;
}
script = [_commentsRegex stringByReplacingMatchesInString:script
options:0
range:NSMakeRange(0, script.length)
withTemplate:@""];
script = [_scriptTagsRegex stringByReplacingMatchesInString:script
options:0
range:NSMakeRange(0, script.length)
withTemplate:@"\\\\<$1\\\\>"];
scriptString = [_commentsRegex stringByReplacingMatchesInString:scriptString
options:0
range:NSMakeRange(0, script.length)
withTemplate:@""];
scriptString = [_scriptTagsRegex stringByReplacingMatchesInString:scriptString
options:0
range:NSMakeRange(0, script.length)
withTemplate:@"\\\\<$1\\\\>"];
NSString *runScript =
[NSString
stringWithFormat:@"<html><head></head><body><script type='text/javascript'>%@</script></body></html>",
script
scriptString
];
[_webView loadHTMLString:runScript baseURL:url];
}
@@ -13,7 +13,7 @@
@interface RCTSourceCode : NSObject <RCTBridgeModule>
@property (nonatomic, copy) NSString *scriptText;
@property (nonatomic, copy) NSData *scriptData;
@property (nonatomic, copy) NSURL *scriptURL;
@end
@@ -27,8 +27,10 @@ - (void)setScriptText:(NSString *)scriptText {}
RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
failureCallback:(RCTResponseErrorBlock)failureCallback)
{
if (RCT_DEV && self.scriptText && self.scriptURL) {
successCallback(@[@{@"text": self.scriptText, @"url": self.scriptURL.absoluteString}]);
if (RCT_DEV && self.scriptData && self.scriptURL) {
NSString *scriptText = [[NSString alloc] initWithData:self.scriptData encoding:NSUTF8StringEncoding];
successCallback(@[@{@"text": scriptText, @"url": self.scriptURL.absoluteString}]);
} else {
failureCallback(RCTErrorWithMessage(@"Source code is not available"));
}

0 comments on commit 4a3857e

Please sign in to comment.