worklist.h 11.7 KB
Newer Older
1 2 3 4
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
#ifndef V8_HEAP_WORKLIST_H_
#define V8_HEAP_WORKLIST_H_
7 8

#include <cstddef>
9
#include <utility>
10

11
#include "src/base/atomic-utils.h"
12 13 14 15 16 17 18 19
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "testing/gtest/include/gtest/gtest_prod.h"  // nogncheck

namespace v8 {
namespace internal {

20
// A concurrent worklist based on segments. Each tasks gets private
21 22 23 24 25 26
// push and pop segments. Empty pop segments are swapped with their
// corresponding push segments. Full push segments are published to a global
// pool of segments and replaced with empty segments.
//
// Work stealing is best effort, i.e., there is no way to inform other tasks
// of the need of items.
27
template <typename EntryType, int SEGMENT_SIZE>
28
class Worklist {
29
 public:
30 31
  class View {
   public:
32
    View(Worklist<EntryType, SEGMENT_SIZE>* worklist, int task_id)
33 34
        : worklist_(worklist), task_id_(task_id) {}

35 36
    // Pushes an entry onto the worklist.
    bool Push(EntryType entry) { return worklist_->Push(task_id_, entry); }
37

38 39
    // Pops an entry from the worklist.
    bool Pop(EntryType* entry) { return worklist_->Pop(task_id_, entry); }
40 41 42 43 44 45

    // Returns true if the local portion of the worklist is empty.
    bool IsLocalEmpty() { return worklist_->IsLocalEmpty(task_id_); }

    // Returns true if the worklist is empty. Can only be used from the main
    // thread without concurrent access.
46
    bool IsEmpty() { return worklist_->IsEmpty(); }
47

48 49
    bool IsGlobalPoolEmpty() { return worklist_->IsGlobalPoolEmpty(); }

50 51 52 53
    size_t LocalPushSegmentSize() {
      return worklist_->LocalPushSegmentSize(task_id_);
    }

54
   private:
55
    Worklist<EntryType, SEGMENT_SIZE>* worklist_;
56 57 58
    int task_id_;
  };

59
  static const int kMaxNumTasks = 8;
60
  static const size_t kSegmentCapacity = SEGMENT_SIZE;
61

62 63 64
  Worklist() : Worklist(kMaxNumTasks) {}

  explicit Worklist(int num_tasks) : num_tasks_(num_tasks) {
65
    DCHECK_LE(num_tasks, kMaxNumTasks);
66
    for (int i = 0; i < num_tasks_; i++) {
67 68
      private_push_segment(i) = NewSegment();
      private_pop_segment(i) = NewSegment();
69 70 71
    }
  }

72
  ~Worklist() {
73
    CHECK(IsEmpty());
74
    for (int i = 0; i < num_tasks_; i++) {
75 76 77 78
      DCHECK_NOT_NULL(private_push_segment(i));
      DCHECK_NOT_NULL(private_pop_segment(i));
      delete private_push_segment(i);
      delete private_pop_segment(i);
79 80 81
    }
  }

82 83 84 85 86 87 88 89 90
  // Swaps content with the given worklist. Local buffers need to
  // be empty, not thread safe.
  void Swap(Worklist<EntryType, SEGMENT_SIZE>& other) {
    CHECK(AreLocalsEmpty());
    CHECK(other.AreLocalsEmpty());

    global_pool_.Swap(other.global_pool_);
  }

91
  bool Push(int task_id, EntryType entry) {
92
    DCHECK_LT(task_id, num_tasks_);
93 94
    DCHECK_NOT_NULL(private_push_segment(task_id));
    if (!private_push_segment(task_id)->Push(entry)) {
95
      PublishPushSegmentToGlobal(task_id);
96
      bool success = private_push_segment(task_id)->Push(entry);
97 98 99 100 101 102
      USE(success);
      DCHECK(success);
    }
    return true;
  }

103
  bool Pop(int task_id, EntryType* entry) {
104
    DCHECK_LT(task_id, num_tasks_);
105 106 107 108 109 110
    DCHECK_NOT_NULL(private_pop_segment(task_id));
    if (!private_pop_segment(task_id)->Pop(entry)) {
      if (!private_push_segment(task_id)->IsEmpty()) {
        Segment* tmp = private_pop_segment(task_id);
        private_pop_segment(task_id) = private_push_segment(task_id);
        private_push_segment(task_id) = tmp;
111 112
      } else if (!StealPopSegmentFromGlobal(task_id)) {
        return false;
113
      }
114
      bool success = private_pop_segment(task_id)->Pop(entry);
115 116 117 118 119 120
      USE(success);
      DCHECK(success);
    }
    return true;
  }

121
  size_t LocalPushSegmentSize(int task_id) {
122
    return private_push_segment(task_id)->Size();
123 124
  }

125
  bool IsLocalEmpty(int task_id) {
126 127
    return private_pop_segment(task_id)->IsEmpty() &&
           private_push_segment(task_id)->IsEmpty();
128 129
  }

130
  bool IsGlobalPoolEmpty() { return global_pool_.IsEmpty(); }
131

132
  bool IsEmpty() {
133 134 135 136 137
    if (!AreLocalsEmpty()) return false;
    return global_pool_.IsEmpty();
  }

  bool AreLocalsEmpty() {
138
    for (int i = 0; i < num_tasks_; i++) {
139 140
      if (!IsLocalEmpty(i)) return false;
    }
141
    return true;
142 143
  }

144
  size_t LocalSize(int task_id) {
145 146
    return private_pop_segment(task_id)->Size() +
           private_push_segment(task_id)->Size();
147 148 149
  }

  // Clears all segments. Frees the global segment pool.
150 151
  //
  // Assumes that no other tasks are running.
152
  void Clear() {
153
    for (int i = 0; i < num_tasks_; i++) {
154 155
      private_pop_segment(i)->Clear();
      private_push_segment(i)->Clear();
156
    }
157
    global_pool_.Clear();
158 159 160
  }

  // Calls the specified callback on each element of the deques and replaces
161 162 163 164 165
  // the element with the result of the callback.
  // The signature of the callback is
  //   bool Callback(EntryType old, EntryType* new).
  // If the callback returns |false| then the element is removed from the
  // worklist. Otherwise the |new| entry is updated.
166 167
  //
  // Assumes that no other tasks are running.
168 169
  template <typename Callback>
  void Update(Callback callback) {
170
    for (int i = 0; i < num_tasks_; i++) {
171 172
      private_pop_segment(i)->Update(callback);
      private_push_segment(i)->Update(callback);
173
    }
174
    global_pool_.Update(callback);
175 176
  }

177 178 179 180 181 182 183 184 185 186 187 188 189 190
  // Calls the specified callback on each element of the deques.
  // The signature of the callback is:
  //   void Callback(EntryType entry).
  //
  // Assumes that no other tasks are running.
  template <typename Callback>
  void Iterate(Callback callback) {
    for (int i = 0; i < num_tasks_; i++) {
      private_pop_segment(i)->Iterate(callback);
      private_push_segment(i)->Iterate(callback);
    }
    global_pool_.Iterate(callback);
  }

191 192
  template <typename Callback>
  void IterateGlobalPool(Callback callback) {
193
    global_pool_.Iterate(callback);
194 195
  }

196 197 198 199 200
  void FlushToGlobal(int task_id) {
    PublishPushSegmentToGlobal(task_id);
    PublishPopSegmentToGlobal(task_id);
  }

201 202 203 204 205
  void MergeGlobalPool(Worklist* other) {
    auto pair = other->global_pool_.Extract();
    global_pool_.MergeList(pair.first, pair.second);
  }

206
 private:
207 208 209 210 211 212 213 214 215 216
  FRIEND_TEST(WorkListTest, SegmentCreate);
  FRIEND_TEST(WorkListTest, SegmentPush);
  FRIEND_TEST(WorkListTest, SegmentPushPop);
  FRIEND_TEST(WorkListTest, SegmentIsEmpty);
  FRIEND_TEST(WorkListTest, SegmentIsFull);
  FRIEND_TEST(WorkListTest, SegmentClear);
  FRIEND_TEST(WorkListTest, SegmentFullPushFails);
  FRIEND_TEST(WorkListTest, SegmentEmptyPopFails);
  FRIEND_TEST(WorkListTest, SegmentUpdateFalse);
  FRIEND_TEST(WorkListTest, SegmentUpdate);
217 218 219

  class Segment {
   public:
220
    static const size_t kCapacity = kSegmentCapacity;
221 222 223

    Segment() : index_(0) {}

224
    bool Push(EntryType entry) {
225
      if (IsFull()) return false;
226
      entries_[index_++] = entry;
227 228 229
      return true;
    }

230
    bool Pop(EntryType* entry) {
231
      if (IsEmpty()) return false;
232
      *entry = entries_[--index_];
233 234 235
      return true;
    }

236 237 238
    size_t Size() const { return index_; }
    bool IsEmpty() const { return index_ == 0; }
    bool IsFull() const { return index_ == kCapacity; }
239 240
    void Clear() { index_ = 0; }

241 242 243 244
    template <typename Callback>
    void Update(Callback callback) {
      size_t new_index = 0;
      for (size_t i = 0; i < index_; i++) {
245 246
        if (callback(entries_[i], &entries_[new_index])) {
          new_index++;
247 248 249 250 251
        }
      }
      index_ = new_index;
    }

252
    template <typename Callback>
253
    void Iterate(Callback callback) const {
254 255 256 257 258
      for (size_t i = 0; i < index_; i++) {
        callback(entries_[i]);
      }
    }

259 260 261
    Segment* next() const { return next_; }
    void set_next(Segment* segment) { next_ = segment; }

262
   private:
263
    Segment* next_;
264
    size_t index_;
265
    EntryType entries_[kCapacity];
266 267
  };

268 269 270 271 272 273
  struct PrivateSegmentHolder {
    Segment* private_push_segment;
    Segment* private_pop_segment;
    char cache_line_padding[64];
  };

274 275 276 277
  class GlobalPool {
   public:
    GlobalPool() : top_(nullptr) {}

278 279 280 281 282 283 284
    // Swaps contents, not thread safe.
    void Swap(GlobalPool& other) {
      Segment* temp = top_;
      set_top(other.top_);
      other.set_top(temp);
    }

285
    V8_INLINE void Push(Segment* segment) {
286
      base::MutexGuard guard(&lock_);
287
      segment->set_next(top_);
288
      set_top(segment);
289 290 291
    }

    V8_INLINE bool Pop(Segment** segment) {
292
      base::MutexGuard guard(&lock_);
293 294
      if (top_ != nullptr) {
        *segment = top_;
295
        set_top(top_->next());
296 297 298 299 300 301
        return true;
      }
      return false;
    }

    V8_INLINE bool IsEmpty() {
302
      return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr;
303 304 305
    }

    void Clear() {
306
      base::MutexGuard guard(&lock_);
307 308 309 310 311 312
      Segment* current = top_;
      while (current != nullptr) {
        Segment* tmp = current;
        current = current->next();
        delete tmp;
      }
313
      set_top(nullptr);
314 315 316 317 318
    }

    // See Worklist::Update.
    template <typename Callback>
    void Update(Callback callback) {
319
      base::MutexGuard guard(&lock_);
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
      Segment* prev = nullptr;
      Segment* current = top_;
      while (current != nullptr) {
        current->Update(callback);
        if (current->IsEmpty()) {
          if (prev == nullptr) {
            top_ = current->next();
          } else {
            prev->set_next(current->next());
          }
          Segment* tmp = current;
          current = current->next();
          delete tmp;
        } else {
          prev = current;
          current = current->next();
        }
      }
    }

    // See Worklist::Iterate.
    template <typename Callback>
    void Iterate(Callback callback) {
343
      base::MutexGuard guard(&lock_);
344 345 346 347 348 349
      for (Segment* current = top_; current != nullptr;
           current = current->next()) {
        current->Iterate(callback);
      }
    }

350 351 352
    std::pair<Segment*, Segment*> Extract() {
      Segment* top = nullptr;
      {
353
        base::MutexGuard guard(&lock_);
354 355 356 357 358 359 360 361 362 363 364 365
        if (top_ == nullptr) return std::make_pair(nullptr, nullptr);
        top = top_;
        set_top(nullptr);
      }
      Segment* end = top;
      while (end->next() != nullptr) end = end->next();
      return std::make_pair(top, end);
    }

    void MergeList(Segment* start, Segment* end) {
      if (start == nullptr) return;
      {
366
        base::MutexGuard guard(&lock_);
367 368 369 370 371
        end->set_next(top_);
        set_top(start);
      }
    }

372
   private:
373
    void set_top(Segment* segment) {
374 375
      base::AsAtomicPointer::Relaxed_Store(&top_, segment);
    }
376

377 378 379 380
    base::Mutex lock_;
    Segment* top_;
  };

381 382 383 384 385 386 387 388
  V8_INLINE Segment*& private_push_segment(int task_id) {
    return private_segments_[task_id].private_push_segment;
  }

  V8_INLINE Segment*& private_pop_segment(int task_id) {
    return private_segments_[task_id].private_pop_segment;
  }

389
  V8_INLINE void PublishPushSegmentToGlobal(int task_id) {
390
    if (!private_push_segment(task_id)->IsEmpty()) {
391
      global_pool_.Push(private_push_segment(task_id));
392
      private_push_segment(task_id) = NewSegment();
393
    }
394 395
  }

396
  V8_INLINE void PublishPopSegmentToGlobal(int task_id) {
397
    if (!private_pop_segment(task_id)->IsEmpty()) {
398
      global_pool_.Push(private_pop_segment(task_id));
399
      private_pop_segment(task_id) = NewSegment();
400 401 402
    }
  }

403
  V8_INLINE bool StealPopSegmentFromGlobal(int task_id) {
404
    if (global_pool_.IsEmpty()) return false;
405 406 407 408 409 410 411
    Segment* new_segment = nullptr;
    if (global_pool_.Pop(&new_segment)) {
      delete private_pop_segment(task_id);
      private_pop_segment(task_id) = new_segment;
      return true;
    }
    return false;
412 413
  }

414 415 416 417 418
  V8_INLINE Segment* NewSegment() {
    // Bottleneck for filtering in crash dumps.
    return new Segment();
  }

419
  PrivateSegmentHolder private_segments_[kMaxNumTasks];
420
  GlobalPool global_pool_;
421
  int num_tasks_;
422 423 424 425 426
};

}  // namespace internal
}  // namespace v8

427
#endif  // V8_HEAP_WORKLIST_H_