Data structure for exclusive writing
Summary: Adds a data structure that holds a series of values that can be *borrowed* for exclusive writing. That means, that only a single consumer can write to any value owned by the data structure. In addition, the data structure exposes read access via iteration over all contained values. A typical use case would be a counter with thread-local values that are accumulated by readers in other parts of a programm. The design carefully avoids the use of atomics or locks for reading and writing. This approach avoids cache flushes and bus sync between cores. Borrowing and returning a value go through a central lock to guarantee the consistency of the underlying data structure. Values are allocated in a `std::forward_list`, which typically should avoid two values in the same cache line -- in that case, writing to one value would still cause cache flushing on other cores. An alternative approach would be to allocate values continuously on cache line boundaries (with padding between them). We can still change the code if the current approach turns out to be too naive (non-deterministic). Reviewed By: SidharthGuglani Differential Revision: D15535018 fbshipit-source-id: 212ac88bba9682a4c9d4326b46de0ee2fb5d9a7e
This commit is contained in:
committed by
Facebook Github Bot
parent
f304990656
commit
0908d3a173
163
util/SingleWriterValueListTest.cpp
Normal file
163
util/SingleWriterValueListTest.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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 <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