From: Nathan Bronson Date: Thu, 19 Dec 2013 18:37:56 +0000 (-0800) Subject: AtomicStruct provides atomic ops on small trivial classes X-Git-Tag: v0.22.0~756 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=790628410e0afadcc904a25e4e855bfd53054099 AtomicStruct provides atomic ops on small trivial classes Summary: AtomicStruct acts like std::atomic, but it supports any trivial or trivially-copyable type up to 8 bytes in size. I'm not sure why these types weren't included in the original std::atomic standard, since this construct removes a lot of boilerplate for some uses of std::atomic. Test Plan: 1. unit tests 2. this code is adapted from production use in tao/queues/LifoSem Reviewed By: davejwatson@fb.com FB internal diff: D1085106 --- diff --git a/folly/AtomicStruct.h b/folly/AtomicStruct.h new file mode 100644 index 00000000..ed4b5077 --- /dev/null +++ b/folly/AtomicStruct.h @@ -0,0 +1,139 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOLLY_ATOMIC_STRUCT_H_ +#define FOLLY_ATOMIC_STRUCT_H_ + +#include +#include +#include +#include +#include + +namespace folly { + +namespace detail { +template struct AtomicStructIntPick {}; +} + +/// AtomicStruct work like C++ atomics, but can be used on any POD +/// type <= 8 bytes. +template < + typename T, + template class Atom = std::atomic, + typename Raw = typename detail::AtomicStructIntPick::type> +class AtomicStruct { + static_assert(alignof(T) <= alignof(Raw), + "target type can't have stricter alignment than matching int"); + static_assert(sizeof(T) <= sizeof(Raw), + "underlying type isn't big enough"); + static_assert(std::is_trivial::value || + folly::IsTriviallyCopyable::value, + "target type must be trivially copyable"); + + Atom data; + + static Raw encode(T v) noexcept { + // we expect the compiler to optimize away the memcpy, but without + // it we would violate strict aliasing rules + Raw d = 0; + memcpy(&d, &v, sizeof(T)); + return d; + } + + static T decode(Raw d) noexcept { + T v; + memcpy(&v, &d, sizeof(T)); + return v; + } + + public: + AtomicStruct() = default; + ~AtomicStruct() = default; + AtomicStruct(AtomicStruct const &) = delete; + AtomicStruct& operator= (AtomicStruct const &) = delete; + + constexpr /* implicit */ AtomicStruct(T v) noexcept : data(encode(v)) {} + + bool is_lock_free() const noexcept { + return data.is_lock_free(); + } + + bool compare_exchange_strong( + T& v0, T v1, + std::memory_order mo = std::memory_order_seq_cst) noexcept { + Raw d0 = encode(v0); + bool rv = data.compare_exchange_strong(d0, encode(v1), mo); + if (!rv) { + v0 = decode(d0); + } + return rv; + } + + bool compare_exchange_weak( + T& v0, T v1, + std::memory_order mo = std::memory_order_seq_cst) noexcept { + Raw d0 = encode(v0); + bool rv = data.compare_exchange_weak(d0, encode(v1), mo); + if (!rv) { + v0 = decode(d0); + } + return rv; + } + + T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept { + return decode(data.exchange(encode(v), mo)); + } + + /* implicit */ operator T () const noexcept { + return decode(data); + } + + T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept { + return decode(data.load(mo)); + } + + T operator= (T v) noexcept { + return decode(data = encode(v)); + } + + void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept { + data.store(encode(v), mo); + } + + // std::atomic also provides volatile versions of all of the access + // methods. These are callable on volatile objects, and also can + // theoretically have different implementations than their non-volatile + // counterpart. If someone wants them here they can easily be added + // by duplicating the above code and the corresponding unit tests. +}; + +namespace detail { + +template <> struct AtomicStructIntPick<1> { typedef uint8_t type; }; +template <> struct AtomicStructIntPick<2> { typedef uint16_t type; }; +template <> struct AtomicStructIntPick<3> { typedef uint32_t type; }; +template <> struct AtomicStructIntPick<4> { typedef uint32_t type; }; +template <> struct AtomicStructIntPick<5> { typedef uint64_t type; }; +template <> struct AtomicStructIntPick<6> { typedef uint64_t type; }; +template <> struct AtomicStructIntPick<7> { typedef uint64_t type; }; +template <> struct AtomicStructIntPick<8> { typedef uint64_t type; }; + +} // namespace detail + +} // namespace folly + +#endif diff --git a/folly/Makefile.am b/folly/Makefile.am index a9647533..45c53716 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -25,6 +25,7 @@ nobase_follyinclude_HEADERS = \ AtomicHashArray-inl.h \ AtomicHashMap.h \ AtomicHashMap-inl.h \ + AtomicStruct.h \ Benchmark.h \ Bits.h \ Chrono.h \ diff --git a/folly/test/AtomicStructTest.cpp b/folly/test/AtomicStructTest.cpp new file mode 100644 index 00000000..9e2f879f --- /dev/null +++ b/folly/test/AtomicStructTest.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "folly/AtomicStruct.h" + +#include +#include + +using namespace folly; + +struct TwoBy32 { + uint32_t left; + uint32_t right; +}; + +TEST(AtomicStruct, two_by_32) { + AtomicStruct a(TwoBy32{ 10, 20 }); + TwoBy32 av = a; + EXPECT_EQ(av.left, 10); + EXPECT_EQ(av.right, 20); + EXPECT_TRUE(a.compare_exchange_strong(av, TwoBy32{ 30, 40 })); + EXPECT_FALSE(a.compare_exchange_weak(av, TwoBy32{ 31, 41 })); + EXPECT_EQ(av.left, 30); + EXPECT_TRUE(a.is_lock_free()); + auto b = a.exchange(TwoBy32{ 50, 60 }); + EXPECT_EQ(b.left, 30); + EXPECT_EQ(b.right, 40); + EXPECT_EQ(a.load().left, 50); + a = TwoBy32{ 70, 80 }; + EXPECT_EQ(a.load().right, 80); + a.store(TwoBy32{ 90, 100 }); + av = a; + EXPECT_EQ(av.left, 90); + AtomicStruct c; + c = b; + EXPECT_EQ(c.load().right, 40); +} + +TEST(AtomicStruct, size_selection) { + struct S1 { char x[1]; }; + struct S2 { char x[2]; }; + struct S3 { char x[3]; }; + struct S4 { char x[4]; }; + struct S5 { char x[5]; }; + struct S6 { char x[6]; }; + struct S7 { char x[7]; }; + struct S8 { char x[8]; }; + + EXPECT_EQ(sizeof(AtomicStruct), 1); + EXPECT_EQ(sizeof(AtomicStruct), 2); + EXPECT_EQ(sizeof(AtomicStruct), 4); + EXPECT_EQ(sizeof(AtomicStruct), 4); + EXPECT_EQ(sizeof(AtomicStruct), 8); + EXPECT_EQ(sizeof(AtomicStruct), 8); + EXPECT_EQ(sizeof(AtomicStruct), 8); + EXPECT_EQ(sizeof(AtomicStruct), 8); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + google::ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +}