Use JSStringCreateWithUTF8CString and skip NSString decoding when loading 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
This commit is contained in:
Frédéric Sagnes 2015-10-16 08:10:25 -07:00 коммит произвёл facebook-github-bot-3
Родитель 8e2ec64763
Коммит 4a3857ef1d
10 изменённых файлов: 47 добавлений и 35 удалений

Просмотреть файл

@ -40,7 +40,7 @@
- (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 @@ static uint64_t _get_time_nanoseconds(void)
} \
";
[_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 @@ RCT_EXPORT_MODULE()
});
}
- (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 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
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 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
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 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
// 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,7 +356,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
callback:onComplete];
}
- (void)executeSourceCode:(NSString *)sourceCode
- (void)executeSourceCode:(NSData *)sourceCode
{
if (!self.valid || !_javaScriptExecutor) {
return;
@ -363,7 +364,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
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 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
}
}
- (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 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
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 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
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 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
onComplete(error, nil);
return;
}
onComplete(nil, rawText);
onComplete(nil, data);
}];
[task resume];

Просмотреть файл

@ -494,7 +494,7 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
}), 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 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
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 @@ RCT_EXPORT_MODULE()
* 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 @@ RCT_EXPORT_MODULE()
}
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 @@ RCT_EXPORT_MODULE()
}];
[_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 @@ RCT_EXPORT_MODULE()
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"));
}