Skip to content

Commit 232517a

Browse files
emilisbfacebook-github-bot
authored andcommitted
Implement nativePerformanceNow to improve Profiler API results (#27885)
Summary: When experimenting with React Profiler API (https://reactjs.org/docs/profiler.html), I noticed that durations are integers without a debugger, but they are doubles with higher precision when debugger is attached. After digging into React Profiler code, I found out that it's using `performance.now()` to accumulate execution times of individual units of work. Since this method does not exist in React Native, it falls back to Javascript `Date`, leading to imprecise results. This PR introduces `global.nativePerformanceNow` function which returns precise native time, and a very basic `performance` polyfill with `now` function. This will greatly improve React Profiler API results, which is essential for profiling and benchmark tools. Solves #27274 ## Changelog [General] [Added] - Implement `nativePerformanceNow` and `performance.now()` Pull Request resolved: #27885 Test Plan: ``` const initialTime = global.performance.now(); setTimeout(() => { const newTime = global.performance.now(); console.warn('duration', newTime - initialTime); }, 1000); ``` ### Android + Hermes ![Screenshot_1580198068](https://user-images.githubusercontent.com/13116854/73245757-af0d6c80-41b5-11ea-8130-dde14ebd41a3.png) ### Android + JSC ![Screenshot_1580199089](https://user-images.githubusercontent.com/13116854/73246157-92256900-41b6-11ea-87a6-ac222383200c.png) ### iOS ![Simulator Screen Shot - iPhone 8 - 2020-01-28 at 10 06 49](https://user-images.githubusercontent.com/13116854/73245871-f136ae00-41b5-11ea-9e31-b1eff5717e62.png) Reviewed By: ejanzer Differential Revision: D19888289 Pulled By: rickhanlonii fbshipit-source-id: ab8152382da9aee9b4b3c76f096e45d40f55da6c
1 parent 367a573 commit 232517a

File tree

10 files changed

+123
-3
lines changed

10 files changed

+123
-3
lines changed

Libraries/Core/InitializeCore.js

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
const start = Date.now();
3030

3131
require('./setUpGlobals');
32+
require('./setUpPerformance');
3233
require('./setUpSystrace');
3334
require('./setUpErrorHandling');
3435
require('./polyfillPromise');

Libraries/Core/setUpPerformance.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
if (!global.performance) {
14+
global.performance = {};
15+
}
16+
17+
/**
18+
* Returns a double, measured in milliseconds.
19+
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
20+
*/
21+
if (typeof global.performance.now !== 'function') {
22+
global.performance.now = function() {
23+
const performanceNow = global.nativePerformanceNow || Date.now;
24+
return performanceNow();
25+
};
26+
}

Libraries/Performance/Systrace.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,25 @@ const userTimingPolyfill = __DEV__
8787
}
8888
: null;
8989

90+
function installPerformanceHooks(polyfill) {
91+
if (polyfill) {
92+
if (global.performance === undefined) {
93+
global.performance = {};
94+
}
95+
96+
Object.keys(polyfill).forEach(methodName => {
97+
if (typeof global.performance[methodName] !== 'function') {
98+
global.performance[methodName] = polyfill[methodName];
99+
}
100+
});
101+
}
102+
}
103+
90104
const Systrace = {
91105
installReactHook() {
92106
if (_enabled) {
93107
if (__DEV__) {
94-
global.performance = userTimingPolyfill;
108+
installPerformanceHooks(userTimingPolyfill);
95109
}
96110
}
97111
_canInstallReactHook = true;
@@ -108,8 +122,8 @@ const Systrace = {
108122
global.nativeTraceEndLegacy(TRACE_TAG_JS_VM_CALLS);
109123
}
110124
if (_canInstallReactHook) {
111-
if (enabled && global.performance === undefined) {
112-
global.performance = userTimingPolyfill;
125+
if (enabled) {
126+
installPerformanceHooks(userTimingPolyfill);
113127
}
114128
}
115129
}

React/CxxBridge/JSCExecutorFactory.mm

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
_RCTLogJavaScriptInternal(static_cast<RCTLogLevel>(logLevel), [NSString stringWithUTF8String:message.c_str()]);
2525
};
2626
react::bindNativeLogger(runtime, iosLoggingBinder);
27+
28+
react::PerformanceNow iosPerformanceNowBinder = []() {
29+
// CACurrentMediaTime() returns the current absolute time, in seconds
30+
return CACurrentMediaTime() * 1000;
31+
};
32+
react::bindNativePerformanceNow(runtime, iosPerformanceNowBinder);
33+
2734
// Wrap over the original runtimeInstaller
2835
if (runtimeInstaller) {
2936
runtimeInstaller(runtime);

ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <react/jni/JReactMarker.h>
1515
#include <react/jni/JSLogging.h>
1616
#include <react/jni/JavaScriptExecutorHolder.h>
17+
#include <react/jni/NativeTime.h>
1718

1819
#include <memory>
1920

@@ -48,6 +49,10 @@ static void installBindings(jsi::Runtime &runtime) {
4849
static_cast<void (*)(const std::string &, unsigned int)>(
4950
&reactAndroidLoggingHook);
5051
react::bindNativeLogger(runtime, androidLogger);
52+
53+
react::PerformanceNow androidNativePerformanceNow =
54+
static_cast<double (*)()>(&reactAndroidNativePerformanceNowHook);
55+
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
5156
}
5257

5358
class HermesExecutorHolder

ReactAndroid/src/main/java/com/facebook/react/jscexecutor/OnLoad.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <react/jni/JReactMarker.h>
1212
#include <react/jni/JSLogging.h>
1313
#include <react/jni/JavaScriptExecutorHolder.h>
14+
#include <react/jni/NativeTime.h>
1415
#include <react/jni/ReadableNativeMap.h>
1516

1617
#include <memory>
@@ -30,6 +31,10 @@ class JSCExecutorFactory : public JSExecutorFactory {
3031
static_cast<void (*)(const std::string &, unsigned int)>(
3132
&reactAndroidLoggingHook);
3233
react::bindNativeLogger(runtime, androidLogger);
34+
35+
react::PerformanceNow androidNativePerformanceNow =
36+
static_cast<double (*)()>(&reactAndroidNativePerformanceNowHook);
37+
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
3338
};
3439
return std::make_unique<JSIExecutor>(
3540
jsc::makeJSCRuntime(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "NativeTime.h"
9+
#include <chrono>
10+
11+
namespace facebook {
12+
namespace react {
13+
14+
double reactAndroidNativePerformanceNowHook() {
15+
auto time = std::chrono::steady_clock::now();
16+
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
17+
time.time_since_epoch())
18+
.count();
19+
20+
constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0;
21+
22+
return duration / NANOSECONDS_IN_MILLISECOND;
23+
}
24+
25+
} // namespace react
26+
} // namespace facebook
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
namespace facebook {
11+
namespace react {
12+
13+
double reactAndroidNativePerformanceNowHook();
14+
15+
} // namespace react
16+
} // namespace facebook

ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -483,5 +483,20 @@ void bindNativeLogger(Runtime &runtime, Logger logger) {
483483
}));
484484
}
485485

486+
void bindNativePerformanceNow(Runtime &runtime, PerformanceNow performanceNow) {
487+
runtime.global().setProperty(
488+
runtime,
489+
"nativePerformanceNow",
490+
Function::createFromHostFunction(
491+
runtime,
492+
PropNameID::forAscii(runtime, "nativePerformanceNow"),
493+
0,
494+
[performanceNow = std::move(performanceNow)](
495+
jsi::Runtime &runtime,
496+
const jsi::Value &,
497+
const jsi::Value *args,
498+
size_t count) { return Value(performanceNow()); }));
499+
}
500+
486501
} // namespace react
487502
} // namespace facebook

ReactCommon/jsiexecutor/jsireact/JSIExecutor.h

+5
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,10 @@ class JSIExecutor : public JSExecutor {
135135
using Logger =
136136
std::function<void(const std::string &message, unsigned int logLevel)>;
137137
void bindNativeLogger(jsi::Runtime &runtime, Logger logger);
138+
139+
using PerformanceNow = std::function<double()>;
140+
void bindNativePerformanceNow(
141+
jsi::Runtime &runtime,
142+
PerformanceNow performanceNow);
138143
} // namespace react
139144
} // namespace facebook

0 commit comments

Comments
 (0)