Add hazptr_obj_batch
authorMaged Michael <magedmichael@fb.com>
Wed, 10 Jan 2018 16:40:46 +0000 (08:40 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 10 Jan 2018 16:50:52 +0000 (08:50 -0800)
Summary: Provide capability for custom batching retirement of objects, for example object that belong to the same data structure, to avoid the risk fragmenting related objects across thread local lists of retired objects of many threads, which could lead to unnecessarily high memory usage.

Reviewed By: djwatson

Differential Revision: D6686603

fbshipit-source-id: fadcade73e71170ef1bcec221c4da6f4ddeecff5

folly/experimental/hazptr/hazptr-impl.h
folly/experimental/hazptr/hazptr.h

index 5c343c5..83f43b6 100644 (file)
@@ -216,14 +216,7 @@ inline void hazptr_obj_base_refcounted<T, D>::retire(
     hazptr_domain& domain,
     D deleter) {
   DEBUG_PRINT(this << " " << &domain);
-  deleter_ = std::move(deleter);
-  reclaim_ = [](hazptr_obj* p) {
-    auto hrobp = static_cast<hazptr_obj_base_refcounted*>(p);
-    if (hrobp->release_ref()) {
-      auto obj = static_cast<T*>(hrobp);
-      hrobp->deleter_(obj);
-    }
-  };
+  preRetire(deleter);
   if (HAZPTR_PRIV &&
       (HAZPTR_ONE_DOMAIN || (&domain == &default_hazptr_domain()))) {
     if (hazptr_priv_try_retire(this)) {
@@ -264,6 +257,19 @@ inline bool hazptr_obj_base_refcounted<T, D>::release_ref() {
   return oldval == 0;
 }
 
+template <typename T, typename D>
+inline void hazptr_obj_base_refcounted<T, D>::preRetire(D deleter) {
+  DCHECK(next_ == nullptr);
+  deleter_ = std::move(deleter);
+  reclaim_ = [](hazptr_obj* p) {
+    auto hrobp = static_cast<hazptr_obj_base_refcounted*>(p);
+    if (hrobp->release_ref()) {
+      auto obj = static_cast<T*>(hrobp);
+      hrobp->deleter_(obj);
+    }
+  };
+}
+
 /**
  *  hazptr_rec
  */
@@ -680,6 +686,7 @@ inline hazptr_domain::~hazptr_domain() {
     while (retired) {
       for (auto p = retired; p; p = next) {
         next = p->next_;
+        DCHECK(p != next);
         DEBUG_PRINT(this << " " << p << " " << p->reclaim_);
         (*(p->reclaim_))(p);
       }
@@ -787,6 +794,7 @@ inline void hazptr_domain::bulkReclaim() {
   hazptr_obj* next;
   for (; p; p = next) {
     next = p->next_;
+    DCHECK(p != next);
     if (hs.count(p->getObjPtr()) == 0) {
       DEBUG_PRINT(this << " " << p << " " << p->reclaim_);
       (*(p->reclaim_))(p);
@@ -1071,5 +1079,73 @@ inline hazptr_tls_life::~hazptr_tls_life() {
   tls_state_ = TLS_DESTROYED;
 }
 
+/** hazptr_obj_batch */
+/*  Only for default domain. Supports only hazptr_obj_base_refcounted
+ *  and a thread-safe access only, for now. */
+
+class hazptr_obj_batch {
+  static constexpr size_t DefaultThreshold = 20;
+  hazptr_obj* head_{nullptr};
+  hazptr_obj* tail_{nullptr};
+  size_t rcount_{0};
+  size_t threshold_{DefaultThreshold};
+
+ public:
+  hazptr_obj_batch() {}
+  hazptr_obj_batch(hazptr_obj* head, hazptr_obj* tail, size_t rcount)
+      : head_(head), tail_(tail), rcount_(rcount) {}
+
+  ~hazptr_obj_batch() {
+    retire_all();
+  }
+
+  /* Prepare a hazptr_obj_base_refcounted for retirement but don't
+       push it the domain yet. Return true if the batch is ready. */
+  template <typename T, typename D = std::default_delete<T>>
+  hazptr_obj_batch prep_retire_refcounted(
+      hazptr_obj_base_refcounted<T, D>* obj,
+      D deleter = {}) {
+    obj->preRetire(deleter);
+    obj->next_ = head_;
+    head_ = obj;
+    if (tail_ == nullptr) {
+      tail_ = obj;
+    }
+    if (++rcount_ < threshold_) {
+      return hazptr_obj_batch();
+    } else {
+      auto head = head_;
+      auto tail = tail_;
+      auto rcount = rcount_;
+      clear();
+      return hazptr_obj_batch(head, tail, rcount);
+    }
+  }
+
+  bool empty() {
+    return rcount_ == 0;
+  }
+
+  void retire_all() {
+    if (!empty()) {
+      auto& domain = default_hazptr_domain();
+      domain.pushRetired(head_, tail_, rcount_);
+      domain.tryBulkReclaim();
+      clear();
+    }
+  }
+
+  void set_threshold(size_t thresh) {
+    threshold_ = thresh;
+  }
+
+ private:
+  void clear() {
+    head_ = nullptr;
+    tail_ = nullptr;
+    rcount_ = 0;
+  }
+};
+
 } // namespace hazptr
 } // namespace folly
index 48e8392..392761e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2017-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@ class hazptr_domain {
   void retire(T* obj, D reclaim = {});
 
  private:
+  friend class hazptr_obj_batch;
   friend class hazptr_holder;
   template <typename, typename>
   friend class hazptr_obj_base;
@@ -100,6 +101,7 @@ void hazptr_retire(T* obj, D reclaim = {});
 
 /** Definition of hazptr_obj */
 class hazptr_obj {
+  friend class hazptr_obj_batch;
   friend class hazptr_domain;
   template <typename, typename>
   friend class hazptr_obj_base;
@@ -108,7 +110,7 @@ class hazptr_obj {
   friend struct hazptr_priv;
 
   void (*reclaim_)(hazptr_obj*);
-  hazptr_obj* next_;
+  hazptr_obj* next_{nullptr}; // nullptr for debugging
 
   const void* getObjPtr() const;
 };
@@ -128,6 +130,8 @@ class hazptr_obj_base : public hazptr_obj {
 /** Definition of hazptr_recounted_obj_base */
 template <typename T, typename D = std::default_delete<T>>
 class hazptr_obj_base_refcounted : public hazptr_obj {
+  friend class hazptr_obj_batch;
+
  public:
   /* Retire a removed object and pass the responsibility for
    * reclaiming it to the hazptr library */
@@ -148,6 +152,8 @@ class hazptr_obj_base_refcounted : public hazptr_obj {
   bool release_ref();
 
  private:
+  void preRetire(D deleter);
+
   std::atomic<uint32_t> refcount_{0};
   D deleter_;
 };