Move SingleWriterValueList to fbandroid (#1196)
Summary: Pull Request resolved: https://github.com/facebook/yoga/pull/1196 This is projected to the Yoga OSS repo as its own top-level directory, and its own static library, but we only ever use this in one specific place in fbandroid. Move this code there and to the same library. Reviewed By: rshest Differential Revision: D42240879 fbshipit-source-id: 97687310339fa05016d98b7c11574ea3e1c2b9a3
This commit is contained in:
committed by
Facebook GitHub Bot
parent
287c48f7e3
commit
fb4917c03c
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "SingleWriterValueList.h"
|
|
||||||
|
|
||||||
namespace facebook {
|
|
||||||
namespace yoga {
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
void* FreeList::getRaw() {
|
|
||||||
if (free_.size() == 0)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto ptr = free_.top();
|
|
||||||
free_.pop();
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FreeList::put(std::mutex& mutex, void* ptr) {
|
|
||||||
std::lock_guard<std::mutex> lock{mutex};
|
|
||||||
free_.push(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
FreeList::FreeList() = default;
|
|
||||||
FreeList::~FreeList() = default;
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
} // namespace yoga
|
|
||||||
} // namespace facebook
|
|
@@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <forward_list>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <stack>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#ifndef YOGA_EXPORT
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#define YOGA_EXPORT
|
|
||||||
#else
|
|
||||||
#define YOGA_EXPORT __attribute__((visibility("default")))
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace facebook {
|
|
||||||
namespace yoga {
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
|
|
||||||
class YOGA_EXPORT FreeList {
|
|
||||||
std::stack<void*> free_;
|
|
||||||
void* getRaw();
|
|
||||||
|
|
||||||
public:
|
|
||||||
FreeList();
|
|
||||||
~FreeList();
|
|
||||||
|
|
||||||
void put(std::mutex&, void*);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T* get() {
|
|
||||||
return static_cast<T*>(getRaw());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
/// SingleWriterValueList is a data structure that holds a list of values. Each
|
|
||||||
/// value can be borrowed for exclusive writing, and will not be exposed to
|
|
||||||
/// another borrower until returned.
|
|
||||||
/// Additionaly, the whole list of values can be accessed for reading via const
|
|
||||||
/// iterators. Read consistency depends on CPU internals, i.e. whether values
|
|
||||||
/// are written to memory atomically.
|
|
||||||
///
|
|
||||||
/// A typical usage scenario would be a set of threads, where each thread
|
|
||||||
/// borrows a value for lock free writing, e.g. as a thread local variable. This
|
|
||||||
/// avoids the usage of atomics, or locking of shared memory, which both can
|
|
||||||
/// lead to increased latency due to CPU cache flushes and waits.
|
|
||||||
///
|
|
||||||
/// Values are heap allocated (via forward_list), which typically will avoid
|
|
||||||
/// multiple values being allocated in the same CPU cache line, which would also
|
|
||||||
/// lead to cache flushing.
|
|
||||||
///
|
|
||||||
/// SingleWriterValueList never deallocates, to guarantee the validity of
|
|
||||||
/// references and iterators. However, memory returned by a borrower can be
|
|
||||||
/// borrowed again.
|
|
||||||
///
|
|
||||||
/// SingleWriterValueList supports return policies as second template parameter,
|
|
||||||
/// i.e. an optional mutation of values after a borrower returns them. The
|
|
||||||
/// default policy is to do nothing. SingleWriterValueList::resetPolicy is a
|
|
||||||
/// convenience method that will move assign the default value of a type.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// static SingleWriterValueList<int> counters;
|
|
||||||
/// thread_local auto localCounter = counters.borrow();
|
|
||||||
///
|
|
||||||
/// /* per thread */
|
|
||||||
/// localCounter =+ n;
|
|
||||||
///
|
|
||||||
/// /* anywhere */
|
|
||||||
/// std::accumulate(counters.begin(), counters.end(), 0);
|
|
||||||
///
|
|
||||||
template <typename T, void (*ReturnPolicy)(T&) = nullptr>
|
|
||||||
class YOGA_EXPORT SingleWriterValueList {
|
|
||||||
std::forward_list<T> values_{};
|
|
||||||
std::mutex acquireMutex_{};
|
|
||||||
detail::FreeList freeValuesList_{};
|
|
||||||
|
|
||||||
T* allocValue() {
|
|
||||||
values_.emplace_front();
|
|
||||||
return &values_.front();
|
|
||||||
}
|
|
||||||
|
|
||||||
void returnRef(T* value) {
|
|
||||||
if (ReturnPolicy != nullptr) {
|
|
||||||
ReturnPolicy(*value);
|
|
||||||
}
|
|
||||||
freeValuesList_.put(acquireMutex_, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
using const_iterator = decltype(values_.cbegin());
|
|
||||||
|
|
||||||
/// RAII representation of a single value, borrowed for exclusive writing.
|
|
||||||
/// Instances cannot be copied, and will return the borrowed value to the
|
|
||||||
/// owner upon destruction.
|
|
||||||
class Borrowed {
|
|
||||||
T* value_;
|
|
||||||
SingleWriterValueList* owner_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Borrowed(T* value, SingleWriterValueList* owner)
|
|
||||||
: value_{value}, owner_{owner} {}
|
|
||||||
~Borrowed() {
|
|
||||||
if (owner_ != nullptr && value_ != nullptr) {
|
|
||||||
owner_->returnRef(value_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Borrowed(Borrowed&& other) = default;
|
|
||||||
Borrowed& operator=(Borrowed&& other) = default;
|
|
||||||
|
|
||||||
// no copies allowed
|
|
||||||
Borrowed(const Borrowed&) = delete;
|
|
||||||
Borrowed& operator=(const Borrowed&) = delete;
|
|
||||||
|
|
||||||
T& get() { return *value_; }
|
|
||||||
T& operator*() { return get(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
Borrowed borrow() {
|
|
||||||
std::lock_guard<std::mutex> lock{acquireMutex_};
|
|
||||||
T* value = freeValuesList_.get<T>();
|
|
||||||
return {value != nullptr ? value : allocValue(), this};
|
|
||||||
}
|
|
||||||
|
|
||||||
const_iterator cbegin() const { return values_.cbegin(); };
|
|
||||||
const_iterator cend() const { return values_.cend(); };
|
|
||||||
const_iterator begin() const { return cbegin(); };
|
|
||||||
const_iterator end() const { return cend(); };
|
|
||||||
|
|
||||||
static void resetPolicy(T& value) { value = std::move(T{}); }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace yoga
|
|
||||||
} // namespace facebook
|
|
@@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <yoga/util/SingleWriterValueList.h>
|
|
||||||
|
|
||||||
#include <numeric>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
namespace facebook {
|
|
||||||
namespace yoga {
|
|
||||||
|
|
||||||
static_assert(
|
|
||||||
!std::is_copy_constructible<SingleWriterValueList<int>>::value,
|
|
||||||
"SingleWriterValueList must not be copyable");
|
|
||||||
static_assert(
|
|
||||||
!std::is_copy_assignable<SingleWriterValueList<int>>::value,
|
|
||||||
"SingleWriterValueList must not be copyable");
|
|
||||||
static_assert(
|
|
||||||
!std::is_copy_constructible<SingleWriterValueList<int>::Borrowed>::value,
|
|
||||||
"SingleWriterValueList::Borrowed must not be copyable");
|
|
||||||
static_assert(
|
|
||||||
!std::is_copy_assignable<SingleWriterValueList<int>::Borrowed>::value,
|
|
||||||
"SingleWriterValueList::Borrowed must not be copyable");
|
|
||||||
static_assert(
|
|
||||||
std::is_move_constructible<SingleWriterValueList<int>::Borrowed>::value,
|
|
||||||
"SingleWriterValueList::Borrowed must be movable");
|
|
||||||
static_assert(
|
|
||||||
std::is_move_assignable<SingleWriterValueList<int>::Borrowed>::value,
|
|
||||||
"SingleWriterValueList::Borrowed must be movable");
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, borrowsAreExclusive) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
auto a = x.borrow();
|
|
||||||
auto b = x.borrow();
|
|
||||||
|
|
||||||
ASSERT_NE(&a.get(), &b.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, borrowsSupportDereference) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
auto a = x.borrow();
|
|
||||||
*a = 123;
|
|
||||||
|
|
||||||
ASSERT_EQ(*a, 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, borrowsHaveGetMethod) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
auto a = x.borrow();
|
|
||||||
a.get() = 123;
|
|
||||||
|
|
||||||
ASSERT_EQ(a.get(), 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, exposesBorrowsViaIterator) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
auto a = x.borrow();
|
|
||||||
auto b = x.borrow();
|
|
||||||
|
|
||||||
*a = 12;
|
|
||||||
*b = 34;
|
|
||||||
|
|
||||||
int sum = 0;
|
|
||||||
for (auto& i : x) {
|
|
||||||
sum += i;
|
|
||||||
}
|
|
||||||
ASSERT_EQ(sum, 12 + 34);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, exposesBorrowsViaConstIterator) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
auto a = x.borrow();
|
|
||||||
auto b = x.borrow();
|
|
||||||
|
|
||||||
*a = 12;
|
|
||||||
*b = 34;
|
|
||||||
|
|
||||||
ASSERT_EQ(std::accumulate(x.cbegin(), x.cend(), 0), 12 + 34);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, doesNotDeallocateReturnedBorrows) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
std::unordered_set<const int*> values;
|
|
||||||
{
|
|
||||||
auto a = x.borrow();
|
|
||||||
auto b = x.borrow();
|
|
||||||
values.insert(&a.get());
|
|
||||||
values.insert(&b.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = x.begin();
|
|
||||||
|
|
||||||
ASSERT_NE(it, x.end());
|
|
||||||
ASSERT_NE(values.find(&*it), values.end());
|
|
||||||
|
|
||||||
ASSERT_NE(++it, x.end());
|
|
||||||
ASSERT_NE(values.find(&*it), values.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, reusesReturnedBorrows) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
int* firstBorrow;
|
|
||||||
{
|
|
||||||
auto a = x.borrow();
|
|
||||||
firstBorrow = &a.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto b = x.borrow();
|
|
||||||
|
|
||||||
ASSERT_EQ(&b.get(), firstBorrow);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, keepsValuesAfterReturning) {
|
|
||||||
SingleWriterValueList<int> x{};
|
|
||||||
|
|
||||||
{
|
|
||||||
auto a = x.borrow();
|
|
||||||
*a = 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_EQ(*x.begin(), 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addOne(int& v) {
|
|
||||||
v += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, allowsCustomReturnPolicy) {
|
|
||||||
SingleWriterValueList<int, addOne> x{};
|
|
||||||
|
|
||||||
{
|
|
||||||
auto a = x.borrow();
|
|
||||||
*a = 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_EQ(*x.begin(), 124);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SingleWriterValueList, hasConvenienceResetPolicy) {
|
|
||||||
SingleWriterValueList<int, SingleWriterValueList<int>::resetPolicy> x{};
|
|
||||||
|
|
||||||
{
|
|
||||||
auto a = x.borrow();
|
|
||||||
*a = 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT_EQ(*x.begin(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace yoga
|
|
||||||
} // namespace facebook
|
|
Reference in New Issue
Block a user