-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: use map for user timing API for better performance
- Loading branch information
Showing
6 changed files
with
349 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and 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 | ||
|
||
#include <numeric> | ||
#include <optional> | ||
#include <string> | ||
#include <unordered_map> | ||
#include <vector> | ||
#include "PerformanceEntry.h" | ||
|
||
namespace facebook::react { | ||
|
||
class ConsumableEntryMap { | ||
public: | ||
ConsumableEntryMap() = default; | ||
|
||
void add(const PerformanceEntry& entry) { | ||
auto& list = entryMap_.at(entry.name); | ||
auto& toConsume = toConsumeMap_.at(entry.name); | ||
list.push_back(entry); | ||
totalEntryCount_ += 1; | ||
totalToConsume_ += 1; | ||
toConsume += 1; | ||
} | ||
|
||
void clear() { | ||
entryMap_.clear(); | ||
toConsumeMap_.clear(); | ||
totalEntryCount_ = 0; | ||
totalToConsume_ = 0; | ||
} | ||
|
||
void clear(const std::string& name) { | ||
if (auto node = entryMap_.find(name); node != entryMap_.end()) { | ||
totalEntryCount_ -= node->second.size(); | ||
totalToConsume_ -= node->second.size(); | ||
toConsumeMap_[name] = 0; | ||
node->second.clear(); | ||
} | ||
} | ||
|
||
std::optional<PerformanceEntry> find(const std::string& name) const { | ||
if (auto node = entryMap_.find(name); node != entryMap_.end()) { | ||
if (!node->second.empty()) { | ||
return std::make_optional<PerformanceEntry>(node->second.back()); | ||
} | ||
} | ||
|
||
return std::nullopt; | ||
} | ||
|
||
void getEntries(std::vector<PerformanceEntry>& target) const { | ||
if (allEntriesCache_.has_value()) { | ||
auto& allEntries = allEntriesCache_.value(); | ||
target.insert(target.end(), allEntries.begin(), allEntries.end()); | ||
return; | ||
} | ||
|
||
std::vector<PerformanceEntry> allEntries; | ||
// pre-allocate result vector | ||
allEntries.reserve(totalEntryCount_); | ||
|
||
for (const auto& [_, entries] : entryMap_) { | ||
allEntries.insert(allEntries.end(), entries.begin(), entries.end()); | ||
} | ||
|
||
std::stable_sort( | ||
allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); | ||
allEntriesCache_ = allEntries; | ||
target.insert(target.end(), allEntries.begin(), allEntries.end()); | ||
} | ||
|
||
/** | ||
* Retrieves buffer entries, whether consumed or not, with predicate | ||
*/ | ||
void consume(std::vector<PerformanceEntry>& target) { | ||
for (const auto& [name, entries] : entryMap_) { | ||
target.insert( | ||
target.end(), entries.end() - toConsumeMap_[name], entries.end()); | ||
toConsumeMap_[name] = 0; | ||
} | ||
} | ||
|
||
size_t getNumToConsume() const { | ||
return totalToConsume_; | ||
} | ||
|
||
void getEntries( | ||
const std::string& name, | ||
std::vector<PerformanceEntry>& target) const { | ||
if (auto node = entryMap_.find(name); node != entryMap_.end()) { | ||
target.insert(target.end(), node->second.begin(), node->second.end()); | ||
} | ||
} | ||
|
||
private: | ||
std::unordered_map<std::string, std::vector<PerformanceEntry>> entryMap_{}; | ||
std::unordered_map<std::string, size_t> toConsumeMap_{}; | ||
mutable std::optional<std::vector<PerformanceEntry>> allEntriesCache_; | ||
size_t totalEntryCount_ = 0; | ||
size_t totalToConsume_ = 0; | ||
}; | ||
|
||
} // namespace facebook::react |
66 changes: 66 additions & 0 deletions
66
packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and 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 | ||
|
||
#include <string> | ||
#include <string_view> | ||
#include <unordered_set> | ||
|
||
namespace facebook::react { | ||
|
||
using DOMHighResTimeStamp = double; | ||
|
||
using PerformanceEntryInteractionId = uint32_t; | ||
|
||
enum class PerformanceEntryType { | ||
// We need to preserve these values for backwards compatibility. | ||
MARK = 1, | ||
MEASURE = 2, | ||
EVENT = 3, | ||
_NEXT = 4, | ||
}; | ||
|
||
struct PerformanceEntry { | ||
std::string name; | ||
PerformanceEntryType entryType; | ||
DOMHighResTimeStamp startTime; | ||
DOMHighResTimeStamp duration = 0; | ||
|
||
// For "event" entries only: | ||
std::optional<DOMHighResTimeStamp> processingStart; | ||
std::optional<DOMHighResTimeStamp> processingEnd; | ||
std::optional<PerformanceEntryInteractionId> interactionId; | ||
}; | ||
|
||
/** | ||
* Status of the add/push operation for the `BoundedConsumableBuffer` | ||
* container | ||
*/ | ||
enum class PerformanceEntryPushStatus { | ||
// There was free space in the buffer, element was successfully pushed: | ||
OK = 0, | ||
|
||
// Element was pushed, but had to overwrite some already consumed elements: | ||
OVERWRITE = 1, | ||
|
||
// Element wasn't pushed, as buffer size limit has been reached and it's | ||
// not possible to overwrite already consumed elements anymore: | ||
DROP = 2, | ||
}; | ||
|
||
struct PerformanceEntrySorter { | ||
bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) { | ||
if (lhs.startTime != rhs.startTime) { | ||
return lhs.startTime < rhs.startTime; | ||
} else { | ||
return lhs.duration < rhs.duration; | ||
} | ||
} | ||
}; | ||
|
||
} // namespace facebook::react |
113 changes: 113 additions & 0 deletions
113
packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and 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 | ||
|
||
#include "BoundedConsumableBuffer.h" | ||
#include "ConsumableEntryMap.h" | ||
#include "PerformanceEntry.h" | ||
|
||
namespace facebook::react { | ||
|
||
// Default duration threshold for reporting performance entries (0 means "report | ||
// all") | ||
constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; | ||
|
||
// Abstract performance entry buffer with reporting flags | ||
struct PerformanceEntryBuffer { | ||
bool isReporting{false}; | ||
bool isAlwaysLogged{false}; | ||
double durationThreshold{DEFAULT_DURATION_THRESHOLD}; | ||
|
||
explicit PerformanceEntryBuffer() = default; | ||
virtual ~PerformanceEntryBuffer() = default; | ||
|
||
virtual PerformanceEntryPushStatus add(const PerformanceEntry&& entry) = 0; | ||
virtual void consume(std::vector<PerformanceEntry>& target) = 0; | ||
virtual size_t pendingMessagesCount() const = 0; | ||
virtual void getEntries( | ||
std::optional<std::string_view> name, | ||
std::vector<PerformanceEntry>& target) const = 0; | ||
virtual void clear() = 0; | ||
virtual void clear(std::string_view name) = 0; | ||
}; | ||
|
||
struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { | ||
BoundedConsumableBuffer<PerformanceEntry> entries; | ||
|
||
explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} | ||
~PerformanceEntryCircularBuffer() override = default; | ||
|
||
PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override { | ||
return entries.add(std::move(entry)); | ||
} | ||
|
||
void consume(std::vector<PerformanceEntry>& target) override { | ||
entries.consume(target); | ||
} | ||
|
||
size_t pendingMessagesCount() const override { | ||
return entries.getNumToConsume(); | ||
} | ||
|
||
void getEntries( | ||
std::optional<std::string_view> name, | ||
std::vector<PerformanceEntry>& target) const override { | ||
entries.getEntries( | ||
target, [&](const PerformanceEntry& e) { return e.name == name; }); | ||
} | ||
|
||
void clear() override { | ||
entries.clear(); | ||
} | ||
|
||
void clear(std::string_view name) override { | ||
entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); | ||
} | ||
}; | ||
|
||
struct PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { | ||
ConsumableEntryMap entries; | ||
|
||
explicit PerformanceEntryKeyedBuffer() = default; | ||
~PerformanceEntryKeyedBuffer() override = default; | ||
|
||
PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override { | ||
entries.add(entry); | ||
return PerformanceEntryPushStatus::OK; | ||
} | ||
|
||
void consume(std::vector<PerformanceEntry>& target) override { | ||
entries.consume(target); | ||
} | ||
|
||
size_t pendingMessagesCount() const override { | ||
return entries.getNumToConsume(); | ||
} | ||
|
||
void getEntries( | ||
std::optional<std::string_view> name, | ||
std::vector<PerformanceEntry>& target) const override { | ||
if (name.has_value()) { | ||
std::string nameStr{name.value()}; | ||
entries.getEntries(nameStr, target); | ||
} else { | ||
entries.getEntries(target); | ||
} | ||
} | ||
|
||
void clear() override { | ||
entries.clear(); | ||
} | ||
|
||
void clear(std::string_view name) override { | ||
std::string nameStr{name}; | ||
entries.clear(nameStr); | ||
} | ||
}; | ||
|
||
} // namespace facebook::react |
Oops, something went wrong.