libunifex icon indicating copy to clipboard operation
libunifex copied to clipboard

Is paring heap a better structure for intrusive heap?

Open SchrodingerZhu opened this issue 3 years ago • 2 comments

The following code implements a variant of paring heap:

template <typename T, T* T::*Next, T* T::*Parent, T* T::*Children, typename Key, Key T::*SortKey>
class intrusive_paring_heap {
 public:
  intrusive_paring_heap() noexcept : root_(nullptr) {}

  bool empty() const noexcept {
    return root_ == nullptr;
  }

  T* top() const noexcept {
    assert(!empty());
    return root_;
  }

  void insert(T* item) noexcept {
    root_ = merge(root_, item);
  }

  T* pop() noexcept {
    assert(!empty());
    auto item = root_;
    T *x, *y, *list = nullptr;
    while ((x = root_->*Children)) {
      if ((root_->*Children = y = x->*Next))
        root_->*Children = root_->*Children->*Next;
      list = push_front(list, merge(x, y));
    }
    x = nullptr;
    while ((y = list)) {
      list = list->*Next;
      x = merge(x, y);
    }
    root_ = x;
    if(root_) root_->*Parent = nullptr;
    item->*Children = item->*Parent = item->*Next = nullptr;
    return item;
  }

  void remove(T* item) noexcept {
    T* parent = item->*Parent;
    if (parent == nullptr) {
      pop();
    }
    else {
      parent->*Children = remove_node(parent->*Children, item);
      if (item->*Children) {
        item->*Children->*Parent = nullptr;
        root_ = merge(root_, item->*Children);
      }
      item->*Children = item->*Parent = item->*Next = nullptr;
    }
  }

 private:
  T* root_;

  static inline T* push_front(T* list, T* x) noexcept {
    x->*Next = list;
    return x;
  }

  static inline T* remove_node(T* list, T* x) noexcept {
    T* p = list;
    if (x == list) return x->*Next;
    while (p && p->*Next != x) p = p->*Next;
    if (p) p->*Next = x->*Next;
    return list;
  }

  static inline T* merge(T* h1, T* h2) noexcept {
    if (!h1) return h2;
    if (!h2) return h1;
    if (h2->*SortKey < h1->*SortKey) std::swap(h1, h2);
    h2->*Next = h1->*Children;
    h1->*Children = h2;
    h2->*Parent = h1;
    h1->*Next = nullptr;
    return h1;
  }
};

However, it seems to have much more branch conditions; and it requires an extra slot of pointer and nullptr initialisation.

(It passes all tests but I didn't test more cases. Also, I didn't whether checkout the binary layout is good enough.)

SchrodingerZhu avatar Aug 26 '20 13:08 SchrodingerZhu

Thanks for the suggestion. The intrusive heap structure is indeed rather naive and placeholder at the moment and won't scale well to large numbers of timers, but was simple to get up and running

I'll need to do some more analysis of this data-structure to determine whether it makes sense to replace what we have with this.

The other data-structure I've been considering adopting for managing priority queues of timers was a hashed hierarchical wheel timer structure, like the one used in folly. It has a larger fixed overhead cost, but O(1) insertion/removal and (I think?) amortised O(1) earliest-time query.

lewissbaker avatar Sep 02 '20 05:09 lewissbaker

Pairing heaps have a bad O(n) worst case (might be relevant for real-time applications).

avdgrinten avatar Sep 03 '20 08:09 avdgrinten