From 232517a5740f5b82cfe8779b3832e9a7a47a8d3d Mon Sep 17 00:00:00 2001 From: Emilis Baliukonis Date: Tue, 31 Mar 2020 10:21:19 -0700 Subject: [PATCH] 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 https://github.com/facebook/react-native/issues/27274 ## Changelog [General] [Added] - Implement `nativePerformanceNow` and `performance.now()` Pull Request resolved: https://github.com/facebook/react-native/pull/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 --- Libraries/Core/InitializeCore.js | 1 + Libraries/Core/setUpPerformance.js | 26 +++++++++++++++++++ Libraries/Performance/Systrace.js | 20 +++++++++++--- React/CxxBridge/JSCExecutorFactory.mm | 7 +++++ .../facebook/hermes/reactexecutor/OnLoad.cpp | 5 ++++ .../com/facebook/react/jscexecutor/OnLoad.cpp | 5 ++++ .../src/main/jni/react/jni/NativeTime.cpp | 26 +++++++++++++++++++ .../src/main/jni/react/jni/NativeTime.h | 16 ++++++++++++ .../jsiexecutor/jsireact/JSIExecutor.cpp | 15 +++++++++++ .../jsiexecutor/jsireact/JSIExecutor.h | 5 ++++ 10 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 Libraries/Core/setUpPerformance.js create mode 100644 ReactAndroid/src/main/jni/react/jni/NativeTime.cpp create mode 100644 ReactAndroid/src/main/jni/react/jni/NativeTime.h diff --git a/Libraries/Core/InitializeCore.js b/Libraries/Core/InitializeCore.js index 8f12f11b8612db..e00120aa4b48a5 100644 --- a/Libraries/Core/InitializeCore.js +++ b/Libraries/Core/InitializeCore.js @@ -29,6 +29,7 @@ const start = Date.now(); require('./setUpGlobals'); +require('./setUpPerformance'); require('./setUpSystrace'); require('./setUpErrorHandling'); require('./polyfillPromise'); diff --git a/Libraries/Core/setUpPerformance.js b/Libraries/Core/setUpPerformance.js new file mode 100644 index 00000000000000..6fd0a1e42b8213 --- /dev/null +++ b/Libraries/Core/setUpPerformance.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +if (!global.performance) { + global.performance = {}; +} + +/** + * Returns a double, measured in milliseconds. + * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now + */ +if (typeof global.performance.now !== 'function') { + global.performance.now = function() { + const performanceNow = global.nativePerformanceNow || Date.now; + return performanceNow(); + }; +} diff --git a/Libraries/Performance/Systrace.js b/Libraries/Performance/Systrace.js index dcc8555c9a55c2..e1db39bbfc5bbc 100644 --- a/Libraries/Performance/Systrace.js +++ b/Libraries/Performance/Systrace.js @@ -87,11 +87,25 @@ const userTimingPolyfill = __DEV__ } : null; +function installPerformanceHooks(polyfill) { + if (polyfill) { + if (global.performance === undefined) { + global.performance = {}; + } + + Object.keys(polyfill).forEach(methodName => { + if (typeof global.performance[methodName] !== 'function') { + global.performance[methodName] = polyfill[methodName]; + } + }); + } +} + const Systrace = { installReactHook() { if (_enabled) { if (__DEV__) { - global.performance = userTimingPolyfill; + installPerformanceHooks(userTimingPolyfill); } } _canInstallReactHook = true; @@ -108,8 +122,8 @@ const Systrace = { global.nativeTraceEndLegacy(TRACE_TAG_JS_VM_CALLS); } if (_canInstallReactHook) { - if (enabled && global.performance === undefined) { - global.performance = userTimingPolyfill; + if (enabled) { + installPerformanceHooks(userTimingPolyfill); } } } diff --git a/React/CxxBridge/JSCExecutorFactory.mm b/React/CxxBridge/JSCExecutorFactory.mm index 411a083dc3596c..d93a28077bbbbc 100644 --- a/React/CxxBridge/JSCExecutorFactory.mm +++ b/React/CxxBridge/JSCExecutorFactory.mm @@ -24,6 +24,13 @@ _RCTLogJavaScriptInternal(static_cast(logLevel), [NSString stringWithUTF8String:message.c_str()]); }; react::bindNativeLogger(runtime, iosLoggingBinder); + + react::PerformanceNow iosPerformanceNowBinder = []() { + // CACurrentMediaTime() returns the current absolute time, in seconds + return CACurrentMediaTime() * 1000; + }; + react::bindNativePerformanceNow(runtime, iosPerformanceNowBinder); + // Wrap over the original runtimeInstaller if (runtimeInstaller) { runtimeInstaller(runtime); diff --git a/ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp index 8b4dabd0f6fe83..d6cbef092de08a 100644 --- a/ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp +++ b/ReactAndroid/src/main/java/com/facebook/hermes/reactexecutor/OnLoad.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -48,6 +49,10 @@ static void installBindings(jsi::Runtime &runtime) { static_cast( &reactAndroidLoggingHook); react::bindNativeLogger(runtime, androidLogger); + + react::PerformanceNow androidNativePerformanceNow = + static_cast(&reactAndroidNativePerformanceNowHook); + react::bindNativePerformanceNow(runtime, androidNativePerformanceNow); } class HermesExecutorHolder diff --git a/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/OnLoad.cpp index f4e1783fee6f51..e05dfec2ef98e4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/OnLoad.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/jscexecutor/OnLoad.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,10 @@ class JSCExecutorFactory : public JSExecutorFactory { static_cast( &reactAndroidLoggingHook); react::bindNativeLogger(runtime, androidLogger); + + react::PerformanceNow androidNativePerformanceNow = + static_cast(&reactAndroidNativePerformanceNowHook); + react::bindNativePerformanceNow(runtime, androidNativePerformanceNow); }; return std::make_unique( jsc::makeJSCRuntime(), diff --git a/ReactAndroid/src/main/jni/react/jni/NativeTime.cpp b/ReactAndroid/src/main/jni/react/jni/NativeTime.cpp new file mode 100644 index 00000000000000..05eb72b49bc02b --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeTime.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NativeTime.h" +#include + +namespace facebook { +namespace react { + +double reactAndroidNativePerformanceNowHook() { + auto time = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast( + time.time_since_epoch()) + .count(); + + constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0; + + return duration / NANOSECONDS_IN_MILLISECOND; +} + +} // namespace react +} // namespace facebook diff --git a/ReactAndroid/src/main/jni/react/jni/NativeTime.h b/ReactAndroid/src/main/jni/react/jni/NativeTime.h new file mode 100644 index 00000000000000..b4d028dd882908 --- /dev/null +++ b/ReactAndroid/src/main/jni/react/jni/NativeTime.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +double reactAndroidNativePerformanceNowHook(); + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp index 0cf45f52df4275..3fb689543de04a 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp +++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp @@ -483,5 +483,20 @@ void bindNativeLogger(Runtime &runtime, Logger logger) { })); } +void bindNativePerformanceNow(Runtime &runtime, PerformanceNow performanceNow) { + runtime.global().setProperty( + runtime, + "nativePerformanceNow", + Function::createFromHostFunction( + runtime, + PropNameID::forAscii(runtime, "nativePerformanceNow"), + 0, + [performanceNow = std::move(performanceNow)]( + jsi::Runtime &runtime, + const jsi::Value &, + const jsi::Value *args, + size_t count) { return Value(performanceNow()); })); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h index 48333c45c1226b..8245e3fd3fe1ec 100644 --- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h +++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h @@ -135,5 +135,10 @@ class JSIExecutor : public JSExecutor { using Logger = std::function; void bindNativeLogger(jsi::Runtime &runtime, Logger logger); + +using PerformanceNow = std::function; +void bindNativePerformanceNow( + jsi::Runtime &runtime, + PerformanceNow performanceNow); } // namespace react } // namespace facebook