// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_ #define BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_ #include #include #include "base/logging.h" namespace base { namespace sequence_manager { namespace internal { template class IntrusiveHeap; // Intended as an opaque wrapper around |index_|. class HeapHandle { public: HeapHandle() : index_(0u) {} bool IsValid() const { return index_ != 0u; } private: template friend class IntrusiveHeap; HeapHandle(size_t index) : index_(index) {} size_t index_; }; // A standard min-heap with the following assumptions: // 1. T has operator <= // 2. T has method void SetHeapHandle(HeapHandle handle) // 3. T has method void ClearHeapHandle() // 4. T is moveable // 5. T is default constructible // 6. The heap size never gets terribly big so reclaiming memory on pop/erase // isn't a priority. // // The reason IntrusiveHeap exists is to provide similar performance to // std::priority_queue while allowing removal of arbitrary elements. template class IntrusiveHeap { public: IntrusiveHeap() : nodes_(kMinimumHeapSize), size_(0) {} ~IntrusiveHeap() { for (size_t i = 1; i <= size_; i++) { MakeHole(i); } } bool empty() const { return size_ == 0; } size_t size() const { return size_; } void Clear() { for (size_t i = 1; i <= size_; i++) { MakeHole(i); } nodes_.resize(kMinimumHeapSize); size_ = 0; } const T& Min() const { DCHECK_GE(size_, 1u); return nodes_[1]; } void Pop() { DCHECK_GE(size_, 1u); MakeHole(1u); size_t top_index = size_--; if (!empty()) MoveHoleDownAndFillWithLeafElement(1u, std::move(nodes_[top_index])); } void insert(T&& element) { size_++; if (size_ >= nodes_.size()) nodes_.resize(nodes_.size() * 2); // Notionally we have a hole in the tree at index |size_|, move this up // to find the right insertion point. MoveHoleUpAndFillWithElement(size_, std::move(element)); } void erase(HeapHandle handle) { DCHECK_GT(handle.index_, 0u); DCHECK_LE(handle.index_, size_); MakeHole(handle.index_); size_t top_index = size_--; if (empty() || top_index == handle.index_) return; if (nodes_[handle.index_] <= nodes_[top_index]) { MoveHoleDownAndFillWithLeafElement(handle.index_, std::move(nodes_[top_index])); } else { MoveHoleUpAndFillWithElement(handle.index_, std::move(nodes_[top_index])); } } void ReplaceMin(T&& element) { // Note |element| might not be a leaf node so we can't use // MoveHoleDownAndFillWithLeafElement. MoveHoleDownAndFillWithElement(1u, std::move(element)); } void ChangeKey(HeapHandle handle, T&& element) { if (nodes_[handle.index_] <= element) { MoveHoleDownAndFillWithLeafElement(handle.index_, std::move(element)); } else { MoveHoleUpAndFillWithElement(handle.index_, std::move(element)); } } // Caution mutating the heap invalidates the iterators. const T* begin() const { return &nodes_[1u]; } const T* end() const { return begin() + size_; } private: enum { // The majority of sets in the scheduler have 0-3 items in them (a few will // have perhaps up to 100), so this means we usually only have to allocate // memory once. kMinimumHeapSize = 4u }; friend class IntrusiveHeapTest; size_t MoveHole(size_t new_hole_pos, size_t old_hole_pos) { DCHECK_GT(new_hole_pos, 0u); DCHECK_LE(new_hole_pos, size_); DCHECK_GT(new_hole_pos, 0u); DCHECK_LE(new_hole_pos, size_); DCHECK_NE(old_hole_pos, new_hole_pos); nodes_[old_hole_pos] = std::move(nodes_[new_hole_pos]); nodes_[old_hole_pos].SetHeapHandle(HeapHandle(old_hole_pos)); return new_hole_pos; } // Notionally creates a hole in the tree at |index|. void MakeHole(size_t index) { DCHECK_GT(index, 0u); DCHECK_LE(index, size_); nodes_[index].ClearHeapHandle(); } void FillHole(size_t hole, T&& element) { DCHECK_GT(hole, 0u); DCHECK_LE(hole, size_); nodes_[hole] = std::move(element); nodes_[hole].SetHeapHandle(HeapHandle(hole)); DCHECK(std::is_heap(begin(), end(), CompareNodes)); } // is_heap requires a strict comparator. static bool CompareNodes(const T& a, const T& b) { return !(a <= b); } // Moves the |hole| up the tree and when the right position has been found // |element| is moved in. void MoveHoleUpAndFillWithElement(size_t hole, T&& element) { DCHECK_GT(hole, 0u); DCHECK_LE(hole, size_); while (hole >= 2u) { size_t parent_pos = hole / 2; if (nodes_[parent_pos] <= element) break; hole = MoveHole(parent_pos, hole); } FillHole(hole, std::move(element)); } // Moves the |hole| down the tree and when the right position has been found // |element| is moved in. void MoveHoleDownAndFillWithElement(size_t hole, T&& element) { DCHECK_GT(hole, 0u); DCHECK_LE(hole, size_); size_t child_pos = hole * 2; while (child_pos < size_) { if (nodes_[child_pos + 1] <= nodes_[child_pos]) child_pos++; if (element <= nodes_[child_pos]) break; hole = MoveHole(child_pos, hole); child_pos *= 2; } if (child_pos == size_ && !(element <= nodes_[child_pos])) hole = MoveHole(child_pos, hole); FillHole(hole, std::move(element)); } // Moves the |hole| down the tree and when the right position has been found // |leaf_element| is moved in. Faster than MoveHoleDownAndFillWithElement // (it does one key comparison per level instead of two) but only valid for // leaf elements (i.e. one of the max values). void MoveHoleDownAndFillWithLeafElement(size_t hole, T&& leaf_element) { DCHECK_GT(hole, 0u); DCHECK_LE(hole, size_); size_t child_pos = hole * 2; while (child_pos < size_) { size_t second_child = child_pos + 1; if (nodes_[second_child] <= nodes_[child_pos]) child_pos = second_child; hole = MoveHole(child_pos, hole); child_pos *= 2; } if (child_pos == size_) hole = MoveHole(child_pos, hole); MoveHoleUpAndFillWithElement(hole, std::move(leaf_element)); } std::vector nodes_; // NOTE we use 1-based indexing size_t size_; }; } // namespace internal } // namespace sequence_manager } // namespace base #endif // BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_