Skip to content

Commit

Permalink
refactor: use map for user timing API for better performance
Browse files Browse the repository at this point in the history
  • Loading branch information
robik committed Jun 27, 2024
1 parent 6bb75c7 commit 2ed73a9
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <algorithm>
#include <functional>
#include <vector>
#include "PerformanceEntry.h"

namespace facebook::react {

Expand All @@ -31,23 +32,9 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024;
template <class T>
class BoundedConsumableBuffer {
public:
/**
* Status of the add/push operation for the `BoundedConsumableBuffer`
* container
*/
enum class PushStatus {
// 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,
};
using PushStatus = PerformanceEntryPushStatus;

BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE)
explicit BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE)
: maxSize_(maxSize) {
entries_.reserve(maxSize_);
}
Expand Down
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
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
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
Loading

0 comments on commit 2ed73a9

Please sign in to comment.