summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/scheduler/base/intrusive_heap.h
blob: 0b2aa9ff95a35593f0454e946e2623c0ebaa33a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2016 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_INTRUSIVE_HEAP_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_INTRUSIVE_HEAP_H_

#include <algorithm>
#include <vector>

#include "base/logging.h"

namespace base {
namespace sequence_manager {

template <typename T>
class IntrusiveHeap;

// Intended as an opaque wrapper around |index_|.
class HeapHandle {
 public:
  HeapHandle() : index_(0u) {}

  bool IsValid() const { return index_ != 0u; }

 private:
  template <typename T>
  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 <typename T>
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<T> nodes_;  // NOTE we use 1-based indexing
  size_t size_;
};

}  // namespace sequence_manager
}  // namespace base

#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_INTRUSIVE_HEAP_H_