Optimize local and bulk management of hazptr_holder-s
authorMaged Michael <magedmichael@fb.com>
Sun, 1 Oct 2017 01:11:41 +0000 (18:11 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Sun, 1 Oct 2017 01:20:16 +0000 (18:20 -0700)
Summary:
Changes:
- Added hazptr_local<M> for optimized management of local hazptr_holder-s.
- Added hazptr_array<M> for optimized management of hazptr_holder-s
- Added benchmarks for hazptr_local and hazptr_array
- Added tests for hazptr_local and hazptr_array
- Changed SWMRList example to use hazptr_local<2> instead of two hazptr_holder-s.
- Updated benchmark performance results.

Reviewed By: davidtgoldblatt

Differential Revision: D5833721

fbshipit-source-id: 154811f67c38abac7342cecb71f829778ccf76b2

folly/experimental/hazptr/bench/HazptrBench.h
folly/experimental/hazptr/example/SWMRList.h
folly/experimental/hazptr/hazptr-impl.h
folly/experimental/hazptr/hazptr.h
folly/experimental/hazptr/test/HazptrTest.cpp

index 22709e7..1b62bc5 100644 (file)
@@ -95,8 +95,9 @@ inline uint64_t bench(std::string name, int ops, const RepFunc& repFn) {
   return res;
 }
 
+const int ops = 1000000;
+
 inline uint64_t listBench(std::string name, int nthreads, int size) {
-  int ops = 100000;
   auto repFn = [&] {
     SWMRListSet<uint64_t> s;
     auto init = [&] {
@@ -116,7 +117,6 @@ inline uint64_t listBench(std::string name, int nthreads, int size) {
 }
 
 inline uint64_t holderBench(std::string name, int nthreads) {
-  int ops = 100000;
   auto repFn = [&] {
     auto init = [] {};
     auto fn = [&](int tid) {
@@ -130,11 +130,40 @@ inline uint64_t holderBench(std::string name, int nthreads) {
   return bench(name, ops, repFn);
 }
 
+template <size_t M>
+inline uint64_t arrayBench(std::string name, int nthreads) {
+  auto repFn = [&] {
+    auto init = [] {};
+    auto fn = [&](int tid) {
+      for (int j = tid; j < 10 * ops; j += nthreads) {
+        hazptr_array<M> a;
+      }
+    };
+    auto endFn = [] {};
+    return run_once(nthreads, init, fn, endFn);
+  };
+  return bench(name, ops, repFn);
+}
+
+template <size_t M>
+inline uint64_t localBench(std::string name, int nthreads) {
+  auto repFn = [&] {
+    auto init = [] {};
+    auto fn = [&](int tid) {
+      for (int j = tid; j < 10 * ops; j += nthreads) {
+        hazptr_local<10> a;
+      }
+    };
+    auto endFn = [] {};
+    return run_once(nthreads, init, fn, endFn);
+  };
+  return bench(name, ops, repFn);
+}
+
 inline uint64_t retireBench(std::string name, int nthreads) {
   struct Foo : hazptr_obj_base<Foo> {
     int x;
   };
-  int ops = 100000;
   auto repFn = [&] {
     auto init = [] {};
     auto fn = [&](int tid) {
@@ -155,10 +184,26 @@ const int sizes[] = {10, 100};
 inline void benches(std::string name) {
   std::cout << "------------------------------------------- " << name << "\n";
   for (int i : nthr) {
-    std::cout << i << " threads -- construct/destruct 10 hazptr_holder-s"
+    std::cout << i << " threads -- 10x construct/destruct hazptr_holder"
               << std::endl;
     holderBench(name + "              ", i);
     holderBench(name + " - dup        ", i);
+    std::cout << i << " threads -- 10x construct/destruct hazptr_array<10>"
+              << std::endl;
+    arrayBench<10>(name + "              ", i);
+    arrayBench<10>(name + " - dup        ", i);
+    std::cout << i << " threads -- 10x construct/destruct hazptr_array<3>"
+              << std::endl;
+    arrayBench<3>(name + "              ", i);
+    arrayBench<3>(name + " - dup        ", i);
+    std::cout << i << " threads -- 10x construct/destruct hazptr_local<10>"
+              << std::endl;
+    localBench<10>(name + "              ", i);
+    localBench<10>(name + " - dup        ", i);
+    std::cout << i << " threads -- 10x construct/destruct hazptr_local<1>"
+              << std::endl;
+    localBench<1>(name + "              ", i);
+    localBench<1>(name + " - dup        ", i);
     std::cout << i << " threads -- allocate/retire/reclaim object" << std::endl;
     retireBench(name + "              ", i);
     retireBench(name + " - dup        ", i);
@@ -175,6 +220,56 @@ inline void benches(std::string name) {
 } // namespace folly
 
 /*
+-------------------------------------------    amb -    tc
+1 threads -- 10x construct/destruct hazptr_holder
+   amb -    tc                   49 ns     46 ns     44 ns
+   amb -    tc - dup             47 ns     45 ns     44 ns
+1 threads -- 10x construct/destruct hazptr_array<10>
+   amb -    tc                  132 ns    122 ns    117 ns
+   amb -    tc - dup            130 ns    122 ns    117 ns
+1 threads -- 10x construct/destruct hazptr_array<3>
+   amb -    tc                   66 ns     64 ns     63 ns
+   amb -    tc - dup             64 ns     64 ns     63 ns
+1 threads -- 10x construct/destruct hazptr_local<10>
+   amb -    tc                   29 ns     27 ns     27 ns
+   amb -    tc - dup             28 ns     27 ns     27 ns
+1 threads -- 10x construct/destruct hazptr_local<1>
+   amb -    tc                   27 ns     27 ns     27 ns
+   amb -    tc - dup             28 ns     28 ns     27 ns
+1 threads -- allocate/retire/reclaim object
+   amb -    tc                   65 ns     62 ns     60 ns
+   amb -    tc - dup             65 ns     60 ns     59 ns
+1 threads -- 10-item list
+   amb -    tc                   21 ns     21 ns     20 ns
+   amb -    tc - dup             22 ns     21 ns     21 ns
+1 threads -- 100-item list
+   amb -    tc                  229 ns    224 ns    220 ns
+   amb -    tc - dup            223 ns    219 ns    216 ns
+10 threads -- 10x construct/destruct hazptr_holder
+   amb -    tc                    9 ns      8 ns      7 ns
+   amb -    tc - dup              9 ns      8 ns      8 ns
+10 threads -- 10x construct/destruct hazptr_array<10>
+   amb -    tc                   27 ns     23 ns     15 ns
+   amb -    tc - dup             26 ns     20 ns     13 ns
+10 threads -- 10x construct/destruct hazptr_array<3>
+   amb -    tc                   11 ns     11 ns      7 ns
+   amb -    tc - dup             11 ns      9 ns      7 ns
+10 threads -- 10x construct/destruct hazptr_local<10>
+   amb -    tc                    5 ns      3 ns      3 ns
+   amb -    tc - dup              3 ns      3 ns      3 ns
+10 threads -- 10x construct/destruct hazptr_local<1>
+   amb -    tc                    3 ns      3 ns      3 ns
+   amb -    tc - dup              5 ns      4 ns      3 ns
+10 threads -- allocate/retire/reclaim object
+   amb -    tc                   17 ns     15 ns     14 ns
+   amb -    tc - dup             17 ns     15 ns     14 ns
+10 threads -- 10-item list
+   amb -    tc                    4 ns      4 ns      2 ns
+   amb -    tc - dup              4 ns      4 ns      3 ns
+10 threads -- 100-item list
+   amb -    tc                   33 ns     31 ns     24 ns
+   amb -    tc - dup             33 ns     32 ns     30 ns
+----------------------------------------------------------
 ------------------------------------------- no amb - no tc
 1 threads -- construct/destruct 10 hazptr_holder-s
 no amb - no tc                 2518 ns   2461 ns   2431 ns
@@ -252,57 +347,5 @@ no amb -    tc - dup             24 ns     23 ns     21 ns
 10 threads -- 100-item list
 no amb -    tc                  215 ns    208 ns    188 ns
 no amb -    tc - dup            215 ns    209 ns    197 ns
-----------------------------------------------------------
--------------------------------------------    amb -    tc
-1 threads -- construct/destruct 10 hazptr_holder-s
-   amb -    tc                   56 ns     54 ns     54 ns
-   amb -    tc - dup             55 ns     54 ns     53 ns
-1 threads -- allocate/retire/reclaim object
-   amb -    tc                   62 ns     61 ns     61 ns
-   amb -    tc - dup             62 ns     61 ns     61 ns
-1 threads -- 10-item list
-   amb -    tc                   36 ns     35 ns     33 ns
-   amb -    tc - dup             37 ns     35 ns     34 ns
-1 threads -- 100-item list
-   amb -    tc                  262 ns    247 ns    230 ns
-   amb -    tc - dup            249 ns    238 ns    230 ns
-10 threads -- construct/destruct 10 hazptr_holder-s
-   amb -    tc                   14 ns     12 ns     11 ns
-   amb -    tc - dup             12 ns     11 ns     11 ns
-10 threads -- allocate/retire/reclaim object
-   amb -    tc                   18 ns     17 ns     15 ns
-   amb -    tc - dup             18 ns     17 ns     15 ns
-10 threads -- 10-item list
-   amb -    tc                    9 ns      8 ns      8 ns
-   amb -    tc - dup              8 ns      8 ns      7 ns
-10 threads -- 100-item list
-   amb -    tc                   52 ns     42 ns     28 ns
-   amb -    tc - dup             44 ns     37 ns     28 ns
-----------------------------------------------------------
--------------------------------------------     one domain
-1 threads -- construct/destruct 10 hazptr_holder-s
-    one domain                   57 ns     56 ns     55 ns
-    one domain - dup             56 ns     54 ns     53 ns
-1 threads -- allocate/retire/reclaim object
-    one domain                   87 ns     71 ns     64 ns
-    one domain - dup             69 ns     68 ns     68 ns
-1 threads -- 10-item list
-    one domain                   32 ns     30 ns     29 ns
-    one domain - dup             31 ns     30 ns     29 ns
-1 threads -- 100-item list
-    one domain                  269 ns    238 ns    226 ns
-    one domain - dup            237 ns    232 ns    227 ns
-10 threads -- construct/destruct 10 hazptr_holder-s
-    one domain                   16 ns     12 ns     10 ns
-    one domain - dup             11 ns     10 ns     10 ns
-10 threads -- allocate/retire/reclaim object
-    one domain                   19 ns     17 ns     16 ns
-    one domain - dup             19 ns     17 ns     15 ns
-10 threads -- 10-item list
-    one domain                    6 ns      5 ns      5 ns
-    one domain - dup              6 ns      5 ns      5 ns
-10 threads -- 100-item list
-    one domain                   40 ns     39 ns     35 ns
-    one domain - dup             40 ns     39 ns     35 ns
 ----------------------------------------------------------
  */
index 83755a6..20aa0e3 100644 (file)
@@ -99,15 +99,16 @@ class SWMRListSet {
 
   /* Used by readers */
   bool contains(const T& val) const {
-    /* Acquire two hazard pointers for hand-over-hand traversal. */
-    hazptr_holder hptr_prev;
-    hazptr_holder hptr_curr;
+    /* Two hazard pointers for hand-over-hand traversal. */
+    hazptr_local<2> hptr;
+    hazptr_holder* hptr_prev = &hptr[0];
+    hazptr_holder* hptr_curr = &hptr[1];
     while (true) {
       auto prev = &head_;
       auto curr = prev->load(std::memory_order_acquire);
       while (true) {
         if (!curr) { return false; }
-        if (!hptr_curr.try_protect(curr, *prev))
+        if (!hptr_curr->try_protect(curr, *prev))
           break;
         auto next = curr->next_.load(std::memory_order_acquire);
         if (prev->load(std::memory_order_acquire) != curr)
@@ -119,20 +120,9 @@ class SWMRListSet {
         }
         prev = &(curr->next_);
         curr = next;
-        /* Swap does not change the values of the owned hazard
-         * pointers themselves. After the swap, The hazard pointer
-         * owned by hptr_prev continues to protect the node that
-         * contains the pointer *prev. The hazard pointer owned by
-         * hptr_curr will continue to protect the node that contains
-         * the old *prev (unless the old prev was &head), which no
-         * longer needs protection, so hptr_curr's hazard pointer is
-         * now free to protect *curr in the next iteration (if curr !=
-         * null).
-         */
-        swap(hptr_curr, hptr_prev);
+        std::swap(hptr_curr, hptr_prev);
       }
     }
-    /* The hazard pointers are released automatically. */
   }
 };
 
index 12c4fd3..043d04a 100644 (file)
@@ -117,17 +117,23 @@ static_assert(
 
 struct hazptr_tc {
   hazptr_tc_entry entry_[HAZPTR_TC_SIZE];
-  int count_;
+  size_t count_;
+#ifndef NDEBUG
+  bool local_;
+#endif
 
  public:
+  hazptr_tc_entry& operator[](size_t i);
   hazptr_rec* get();
   bool put(hazptr_rec* hprec);
+  size_t count();
 };
 
 static_assert(
     std::is_trivial<hazptr_tc>::value,
     "hazptr_tc must be trivial to avoid a branch to check initialization");
 
+hazptr_tc* hazptr_tc_tls();
 void hazptr_tc_init();
 void hazptr_tc_shutdown();
 hazptr_rec* hazptr_tc_try_get();
@@ -243,7 +249,7 @@ FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(hazptr_domain& domain) {
   if (hazptr_ == nullptr) { std::bad_alloc e; throw e; }
 }
 
-FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(std::nullptr_t) {
+FOLLY_ALWAYS_INLINE hazptr_holder::hazptr_holder(std::nullptr_t) noexcept {
   domain_ = nullptr;
   hazptr_ = nullptr;
   DEBUG_PRINT(this << " " << domain_ << " " << hazptr_);
@@ -350,6 +356,183 @@ FOLLY_ALWAYS_INLINE void swap(hazptr_holder& lhs, hazptr_holder& rhs) noexcept {
   lhs.swap(rhs);
 }
 
+/**
+ *  hazptr_array
+ */
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array() {
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  if (HAZPTR_TC) {
+    auto ptc = hazptr_tc_tls();
+    if (LIKELY(ptc != nullptr)) {
+      auto& tc = *ptc;
+      auto count = tc.count();
+      if (M <= count) {
+        size_t offset = count - M;
+        for (size_t i = 0; i < M; ++i) {
+          auto hprec = tc[offset + i].hprec_;
+          DCHECK(hprec != nullptr);
+          DEBUG_PRINT(i << " " << &h[i]);
+          new (&h[i]) hazptr_holder(nullptr);
+          h[i].hazptr_ = hprec;
+          DEBUG_PRINT(
+              i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+        }
+        tc.count_ = offset;
+        return;
+      }
+    }
+  }
+  // slow path
+  for (size_t i = 0; i < M; ++i) {
+    new (&h[i]) hazptr_holder;
+    DEBUG_PRINT(
+        i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+  }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array(
+    hazptr_array&& other) noexcept {
+  DEBUG_PRINT(this << " " << M << " " << &other);
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  for (size_t i = 0; i < M; ++i) {
+    new (&h[i]) hazptr_holder(std::move(other.h_[i]));
+    DEBUG_PRINT(i << " " << &h[i] << " " << &other.h_[i]);
+  }
+  empty_ = other.empty_;
+  other.empty_ = true;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::hazptr_array(std::nullptr_t) noexcept {
+  DEBUG_PRINT(this << " " << M);
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  for (size_t i = 0; i < M; ++i) {
+    new (&h[i]) hazptr_holder(nullptr);
+    DEBUG_PRINT(i << " " << &h[i]);
+  }
+  empty_ = true;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>::~hazptr_array() {
+  if (empty_) {
+    return;
+  }
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  if (HAZPTR_TC) {
+    auto ptc = hazptr_tc_tls();
+    if (LIKELY(ptc != nullptr)) {
+      auto& tc = *ptc;
+      auto count = tc.count();
+      if (count + M <= HAZPTR_TC_SIZE) {
+        for (size_t i = 0; i < M; ++i) {
+          tc[count + i].hprec_ = h[i].hazptr_;
+          DEBUG_PRINT(i << " " << &h[i]);
+          new (&h[i]) hazptr_holder(nullptr);
+          DEBUG_PRINT(
+              i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+        }
+        tc.count_ = count + M;
+        return;
+      }
+    }
+  }
+  // slow path
+  for (size_t i = 0; i < M; ++i) {
+    h[i].~hazptr_holder();
+  }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_array<M>& hazptr_array<M>::operator=(
+    hazptr_array&& other) noexcept {
+  DEBUG_PRINT(this << " " << M << " " << &other);
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  for (size_t i = 0; i < M; ++i) {
+    h[i] = std::move(other[i]);
+    DEBUG_PRINT(i << " " << &h[i] << " " << &other[i]);
+  }
+  return *this;
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_holder& hazptr_array<M>::operator[](
+    size_t i) noexcept {
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  DCHECK(i < M);
+  return h[i];
+}
+
+/**
+ *  hazptr_local
+ */
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_local<M>::hazptr_local() {
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  if (HAZPTR_TC) {
+    auto ptc = hazptr_tc_tls();
+    if (LIKELY(ptc != nullptr)) {
+      auto& tc = *ptc;
+      auto count = tc.count();
+      if (M <= count) {
+#ifndef NDEBUG
+        DCHECK(!tc.local_);
+        tc.local_ = true;
+#endif
+        // Fast path
+        for (size_t i = 0; i < M; ++i) {
+          auto hprec = tc[i].hprec_;
+          DCHECK(hprec != nullptr);
+          DEBUG_PRINT(i << " " << &h[i]);
+          new (&h[i]) hazptr_holder(nullptr);
+          h[i].hazptr_ = hprec;
+          DEBUG_PRINT(
+              i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+        }
+        return;
+      }
+    }
+  }
+  // Slow path
+  need_destruct_ = true;
+  for (size_t i = 0; i < M; ++i) {
+    new (&h[i]) hazptr_holder;
+    DEBUG_PRINT(
+        i << " " << &h[i] << " " << h[i].domain_ << " " << h[i].hazptr_);
+  }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_local<M>::~hazptr_local() {
+  if (LIKELY(!need_destruct_)) {
+#ifndef NDEBUG
+    auto ptc = hazptr_tc_tls();
+    DCHECK(ptc != nullptr);
+    auto& tc = *ptc;
+    DCHECK(tc.local_);
+    tc.local_ = false;
+#endif
+    return;
+  }
+  // Slow path
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  for (size_t i = 0; i < M; ++i) {
+    h[i].~hazptr_holder();
+  }
+}
+
+template <size_t M>
+FOLLY_ALWAYS_INLINE hazptr_holder& hazptr_local<M>::operator[](
+    size_t i) noexcept {
+  auto h = reinterpret_cast<hazptr_holder*>(&raw_);
+  DCHECK(i < M);
+  return h[i];
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // [TODO]:
 // - Control of reclamation (when and by whom)
@@ -633,6 +816,11 @@ inline void hazptr_tc_entry::evict() {
 
 /** hazptr_tc */
 
+FOLLY_ALWAYS_INLINE hazptr_tc_entry& hazptr_tc::operator[](size_t i) {
+  DCHECK(i <= HAZPTR_TC_SIZE);
+  return entry_[i];
+}
+
 FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc::get() {
   if (LIKELY(count_ != 0)) {
     auto hprec = entry_[--count_].get();
@@ -652,48 +840,64 @@ FOLLY_ALWAYS_INLINE bool hazptr_tc::put(hazptr_rec* hprec) {
   return false;
 }
 
+FOLLY_ALWAYS_INLINE size_t hazptr_tc::count() {
+  return count_;
+}
+
 /** hazptr_tc free functions */
 
-FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc_try_get() {
-  DEBUG_PRINT(TLS_UNINITIALIZED << TLS_ALIVE << TLS_DESTROYED);
+FOLLY_ALWAYS_INLINE hazptr_tc* hazptr_tc_tls() {
   DEBUG_PRINT(tls_state_);
   if (LIKELY(tls_state_ == TLS_ALIVE)) {
     DEBUG_PRINT(tls_state_);
-    return tls_tc_data_.get();
+    return &tls_tc_data_;
   } else if (tls_state_ == TLS_UNINITIALIZED) {
     tls_life_odr_use();
-    return tls_tc_data_.get();
+    return &tls_tc_data_;
   }
   return nullptr;
 }
 
-FOLLY_ALWAYS_INLINE bool hazptr_tc_try_put(hazptr_rec* hprec) {
-  DEBUG_PRINT(tls_state_);
-  if (LIKELY(tls_state_ == TLS_ALIVE)) {
-    DEBUG_PRINT(tls_state_);
-    return tls_tc_data_.put(hprec);
-  }
-  return false;
-}
-
 inline void hazptr_tc_init() {
   DEBUG_PRINT("");
   auto& tc = tls_tc_data_;
   DEBUG_PRINT(&tc);
   tc.count_ = 0;
-  for (int i = 0; i < HAZPTR_TC_SIZE; ++i) {
-    tc.entry_[i].hprec_ = nullptr;
-  }
+#ifndef NDEBUG
+  tc.local_ = false;
+#endif
 }
 
 inline void hazptr_tc_shutdown() {
   auto& tc = tls_tc_data_;
   DEBUG_PRINT(&tc);
-  for (int i = 0; i < tc.count_; ++i) {
+  for (size_t i = 0; i < tc.count_; ++i) {
     tc.entry_[i].evict();
   }
 }
 
+FOLLY_ALWAYS_INLINE hazptr_rec* hazptr_tc_try_get() {
+  DEBUG_PRINT(TLS_UNINITIALIZED << TLS_ALIVE << TLS_DESTROYED);
+  DEBUG_PRINT(tls_state_);
+  if (LIKELY(tls_state_ == TLS_ALIVE)) {
+    DEBUG_PRINT(tls_state_);
+    return tls_tc_data_.get();
+  } else if (tls_state_ == TLS_UNINITIALIZED) {
+    tls_life_odr_use();
+    return tls_tc_data_.get();
+  }
+  return nullptr;
+}
+
+FOLLY_ALWAYS_INLINE bool hazptr_tc_try_put(hazptr_rec* hprec) {
+  DEBUG_PRINT(tls_state_);
+  if (LIKELY(tls_state_ == TLS_ALIVE)) {
+    DEBUG_PRINT(tls_state_);
+    return tls_tc_data_.put(hprec);
+  }
+  return false;
+}
+
 /**
  *  hazptr_priv
  */
index 456fa6c..f1776e4 100644 (file)
@@ -34,6 +34,15 @@ class hazptr_obj;
 template <typename T, typename Deleter>
 class hazptr_obj_base;
 
+/** hazptr_local: Optimized template for bulk construction and destruction of
+ *  hazard pointers */
+template <size_t M>
+class hazptr_array;
+
+/** hazptr_local: Optimized template for locally-used hazard pointers */
+template <size_t M>
+class hazptr_local;
+
 /** hazptr_domain: Class of hazard pointer domains. Each domain manages a set
  *  of hazard pointers and a set of retired objects. */
 class hazptr_domain {
@@ -100,12 +109,17 @@ class hazptr_obj_base : public hazptr_obj {
 /** hazptr_holder: Class for automatic acquisition and release of
  *  hazard pointers, and interface for hazard pointer operations. */
 class hazptr_holder {
+  template <size_t M>
+  friend class hazptr_array;
+  template <size_t M>
+  friend class hazptr_local;
+
  public:
   /* Constructor automatically acquires a hazard pointer. */
   explicit hazptr_holder(hazptr_domain& domain = default_hazptr_domain());
   /* Construct an empty hazptr_holder. */
   // Note: This diverges from the proposal in P0233R4
-  explicit hazptr_holder(std::nullptr_t);
+  explicit hazptr_holder(std::nullptr_t) noexcept;
 
   /* Destructor automatically clears and releases the owned hazard pointer. */
   ~hazptr_holder();
@@ -154,6 +168,69 @@ class hazptr_holder {
 
 void swap(hazptr_holder&, hazptr_holder&) noexcept;
 
+using aligned_hazptr_holder = typename std::
+    aligned_storage<sizeof(hazptr_holder), alignof(hazptr_holder)>::type;
+
+/**
+ *  hazptr_array: Optimized for bulk construction and destruction of
+ *  hazptr_holder-s.
+ *
+ *  WARNING: Do not move from or to individual hazptr_holder-s.
+ *  Only move the whole hazptr_array.
+ */
+template <size_t M = 1>
+class hazptr_array {
+  static_assert(M > 0, "M must be a positive integer.");
+
+ public:
+  hazptr_array();
+  explicit hazptr_array(std::nullptr_t) noexcept;
+
+  hazptr_array(const hazptr_array&) = delete;
+  hazptr_array& operator=(const hazptr_array&) = delete;
+  hazptr_array(hazptr_array&& other) noexcept;
+  hazptr_array& operator=(hazptr_array&& other) noexcept;
+
+  ~hazptr_array();
+
+  hazptr_holder& operator[](size_t i) noexcept;
+
+ private:
+  aligned_hazptr_holder raw_[M];
+  bool empty_{false};
+};
+
+/**
+ *  hazptr_local: Optimized for construction and destruction of
+ *  one or more hazptr_holder-s with local scope.
+ *
+ *  WARNING 1: Do not move from or to individual hazptr_holder-s.
+ *
+ *  WARNING 2: There can only be one hazptr_local active for the same
+ *  thread at any time. This is not tracked and checked by the
+ *  implementation because it would negate the performance gains of
+ *  this class.
+ */
+template <size_t M = 1>
+class hazptr_local {
+  static_assert(M > 0, "M must be a positive integer.");
+
+ public:
+  hazptr_local();
+  hazptr_local(const hazptr_local&) = delete;
+  hazptr_local& operator=(const hazptr_local&) = delete;
+  hazptr_local(hazptr_local&&) = delete;
+  hazptr_local& operator=(hazptr_local&&) = delete;
+
+  ~hazptr_local();
+
+  hazptr_holder& operator[](size_t i) noexcept;
+
+ private:
+  aligned_hazptr_holder raw_[M];
+  bool need_destruct_{false};
+};
+
 } // namespace hazptr
 } // namespace folly
 
index 04cadd3..8468ad5 100644 (file)
@@ -348,3 +348,49 @@ TEST_F(HazptrTest, Move) {
     hptr2.reset();
   }
 }
+
+TEST_F(HazptrTest, Array) {
+  struct Foo : hazptr_obj_base<Foo> {
+    int a;
+  };
+  for (int i = 0; i < 100; ++i) {
+    Foo* x = new Foo;
+    x->a = i;
+    hazptr_array<10> hptr;
+    // Protect object
+    hptr[9].reset(x);
+    // Empty array
+    hazptr_array<10> h;
+    // Move assignment
+    h = std::move(hptr);
+    // Retire object
+    x->retire();
+    // Unprotect object - hptr2 is nonempty
+    h[9].reset();
+  }
+  {
+    // Abnormal case
+    hazptr_array<HAZPTR_TC_SIZE + 1> h;
+  }
+}
+
+TEST_F(HazptrTest, Local) {
+  struct Foo : hazptr_obj_base<Foo> {
+    int a;
+  };
+  for (int i = 0; i < 100; ++i) {
+    Foo* x = new Foo;
+    x->a = i;
+    hazptr_local<10> hptr;
+    // Protect object
+    hptr[9].reset(x);
+    // Retire object
+    x->retire();
+    // Unprotect object - hptr2 is nonempty
+    hptr[9].reset();
+  }
+  {
+    // Abnormal case
+    hazptr_local<HAZPTR_TC_SIZE + 1> h;
+  }
+}