Skip to content

Commit

Permalink
Implement nativePerformanceNow to improve Profiler API results (#27885)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
emilisb authored and facebook-github-bot committed Mar 31, 2020
1 parent 367a573 commit 232517a
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 3 deletions.
1 change: 1 addition & 0 deletions Libraries/Core/InitializeCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
const start = Date.now();

require('./setUpGlobals');
require('./setUpPerformance');
require('./setUpSystrace');
require('./setUpErrorHandling');
require('./polyfillPromise');
Expand Down
26 changes: 26 additions & 0 deletions Libraries/Core/setUpPerformance.js
Original file line number Diff line number Diff line change
@@ -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();
};
}
20 changes: 17 additions & 3 deletions Libraries/Performance/Systrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions React/CxxBridge/JSCExecutorFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
_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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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(),
Expand Down
26 changes: 26 additions & 0 deletions ReactAndroid/src/main/jni/react/jni/NativeTime.cpp
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions ReactAndroid/src/main/jni/react/jni/NativeTime.h
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions ReactCommon/jsiexecutor/jsireact/JSIExecutor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 232517a

Please sign in to comment.