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
This commit is contained in:
Родитель
367a573ccc
Коммит
232517a574
|
@ -29,6 +29,7 @@
|
|||
const start = Date.now();
|
||||
|
||||
require('./setUpGlobals');
|
||||
require('./setUpPerformance');
|
||||
require('./setUpSystrace');
|
||||
require('./setUpErrorHandling');
|
||||
require('./polyfillPromise');
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,13 @@ std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
|||
_RCTLogJavaScriptInternal(static_cast<RCTLogLevel>(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);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <react/jni/JReactMarker.h>
|
||||
#include <react/jni/JSLogging.h>
|
||||
#include <react/jni/JavaScriptExecutorHolder.h>
|
||||
#include <react/jni/NativeTime.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -48,6 +49,10 @@ static void installBindings(jsi::Runtime &runtime) {
|
|||
static_cast<void (*)(const std::string &, unsigned int)>(
|
||||
&reactAndroidLoggingHook);
|
||||
react::bindNativeLogger(runtime, androidLogger);
|
||||
|
||||
react::PerformanceNow androidNativePerformanceNow =
|
||||
static_cast<double (*)()>(&reactAndroidNativePerformanceNowHook);
|
||||
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
|
||||
}
|
||||
|
||||
class HermesExecutorHolder
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <react/jni/JReactMarker.h>
|
||||
#include <react/jni/JSLogging.h>
|
||||
#include <react/jni/JavaScriptExecutorHolder.h>
|
||||
#include <react/jni/NativeTime.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
|
||||
#include <memory>
|
||||
|
@ -30,6 +31,10 @@ class JSCExecutorFactory : public JSExecutorFactory {
|
|||
static_cast<void (*)(const std::string &, unsigned int)>(
|
||||
&reactAndroidLoggingHook);
|
||||
react::bindNativeLogger(runtime, androidLogger);
|
||||
|
||||
react::PerformanceNow androidNativePerformanceNow =
|
||||
static_cast<double (*)()>(&reactAndroidNativePerformanceNowHook);
|
||||
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
|
||||
};
|
||||
return std::make_unique<JSIExecutor>(
|
||||
jsc::makeJSCRuntime(),
|
||||
|
|
|
@ -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 <chrono>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
double reactAndroidNativePerformanceNowHook() {
|
||||
auto time = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
time.time_since_epoch())
|
||||
.count();
|
||||
|
||||
constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0;
|
||||
|
||||
return duration / NANOSECONDS_IN_MILLISECOND;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -135,5 +135,10 @@ class JSIExecutor : public JSExecutor {
|
|||
using Logger =
|
||||
std::function<void(const std::string &message, unsigned int logLevel)>;
|
||||
void bindNativeLogger(jsi::Runtime &runtime, Logger logger);
|
||||
|
||||
using PerformanceNow = std::function<double()>;
|
||||
void bindNativePerformanceNow(
|
||||
jsi::Runtime &runtime,
|
||||
PerformanceNow performanceNow);
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
|
Загрузка…
Ссылка в новой задаче