Commit 4300eec7 authored by Etienne Pierre-doray's avatar Etienne Pierre-doray Committed by Commit Bot

[v8 heap]: Track GlobalSize in worklist.

GlobalSize will be used as a hint to schedule scavenger work in
https://chromium-review.googlesource.com/c/v8/v8/+/2036661

This is implemented as an atomic variable that's updated when adding/removing
segments.

Bug: chromium:1012816
Change-Id: I8f6c3f10612f8febda9bfe640d91e235aa3c2f12
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2043273Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66175}
parent 1d54a600
...@@ -145,6 +145,9 @@ class Worklist { ...@@ -145,6 +145,9 @@ class Worklist {
private_push_segment(task_id)->Size(); private_push_segment(task_id)->Size();
} }
// Thread-safe but may return an outdated result.
size_t GlobalPoolSize() const { return global_pool_.Size(); }
// Clears all segments. Frees the global segment pool. // Clears all segments. Frees the global segment pool.
// //
// Assumes that no other tasks are running. // Assumes that no other tasks are running.
...@@ -198,8 +201,7 @@ class Worklist { ...@@ -198,8 +201,7 @@ class Worklist {
} }
void MergeGlobalPool(Worklist* other) { void MergeGlobalPool(Worklist* other) {
auto pair = other->global_pool_.Extract(); global_pool_.Merge(&other->global_pool_);
global_pool_.MergeList(pair.first, pair.second);
} }
private: private:
...@@ -279,17 +281,23 @@ class Worklist { ...@@ -279,17 +281,23 @@ class Worklist {
Segment* temp = top_; Segment* temp = top_;
set_top(other.top_); set_top(other.top_);
other.set_top(temp); other.set_top(temp);
size_t other_size = other.size_.exchange(
size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
size_.store(other_size, std::memory_order_relaxed);
} }
V8_INLINE void Push(Segment* segment) { V8_INLINE void Push(Segment* segment) {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
segment->set_next(top_); segment->set_next(top_);
set_top(segment); set_top(segment);
size_.fetch_add(1, std::memory_order_relaxed);
} }
V8_INLINE bool Pop(Segment** segment) { V8_INLINE bool Pop(Segment** segment) {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
if (top_ != nullptr) { if (top_ != nullptr) {
DCHECK_LT(0U, size_);
size_.fetch_sub(1, std::memory_order_relaxed);
*segment = top_; *segment = top_;
set_top(top_->next()); set_top(top_->next());
return true; return true;
...@@ -301,8 +309,16 @@ class Worklist { ...@@ -301,8 +309,16 @@ class Worklist {
return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr; return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr;
} }
V8_INLINE size_t Size() const {
// It is safe to read |size_| without a lock since this variable is
// atomic, keeping in mind that threads may not immediately see the new
// value when it is updated.
return size_.load(std::memory_order_relaxed);
}
void Clear() { void Clear() {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
size_.store(0, std::memory_order_relaxed);
Segment* current = top_; Segment* current = top_;
while (current != nullptr) { while (current != nullptr) {
Segment* tmp = current; Segment* tmp = current;
...@@ -318,9 +334,12 @@ class Worklist { ...@@ -318,9 +334,12 @@ class Worklist {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
Segment* prev = nullptr; Segment* prev = nullptr;
Segment* current = top_; Segment* current = top_;
size_t num_deleted = 0;
while (current != nullptr) { while (current != nullptr) {
current->Update(callback); current->Update(callback);
if (current->IsEmpty()) { if (current->IsEmpty()) {
DCHECK_LT(0U, size_);
++num_deleted;
if (prev == nullptr) { if (prev == nullptr) {
top_ = current->next(); top_ = current->next();
} else { } else {
...@@ -334,6 +353,7 @@ class Worklist { ...@@ -334,6 +353,7 @@ class Worklist {
current = current->next(); current = current->next();
} }
} }
size_.fetch_sub(num_deleted, std::memory_order_relaxed);
} }
// See Worklist::Iterate. // See Worklist::Iterate.
...@@ -346,25 +366,28 @@ class Worklist { ...@@ -346,25 +366,28 @@ class Worklist {
} }
} }
std::pair<Segment*, Segment*> Extract() { void Merge(GlobalPool* other) {
Segment* top = nullptr; Segment* top = nullptr;
size_t other_size = 0;
{ {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&other->lock_);
if (top_ == nullptr) return std::make_pair(nullptr, nullptr); if (!other->top_) return;
top = top_; top = other->top_;
set_top(nullptr); other_size = other->size_.load(std::memory_order_relaxed);
other->size_.store(0, std::memory_order_relaxed);
other->set_top(nullptr);
} }
// It's safe to iterate through these segments because the top was
// extracted from |other|.
Segment* end = top; Segment* end = top;
while (end->next() != nullptr) end = end->next(); while (end->next()) end = end->next();
return std::make_pair(top, end);
}
void MergeList(Segment* start, Segment* end) {
if (start == nullptr) return;
{ {
base::MutexGuard guard(&lock_); base::MutexGuard guard(&lock_);
size_.fetch_add(other_size, std::memory_order_relaxed);
end->set_next(top_); end->set_next(top_);
set_top(start); set_top(top);
} }
} }
...@@ -375,6 +398,7 @@ class Worklist { ...@@ -375,6 +398,7 @@ class Worklist {
base::Mutex lock_; base::Mutex lock_;
Segment* top_; Segment* top_;
std::atomic<size_t> size_{0};
}; };
V8_INLINE Segment*& private_push_segment(int task_id) { V8_INLINE Segment*& private_push_segment(int task_id) {
......
...@@ -148,13 +148,16 @@ TEST(WorkListTest, LocalPushStaysPrivate) { ...@@ -148,13 +148,16 @@ TEST(WorkListTest, LocalPushStaysPrivate) {
SomeObject dummy; SomeObject dummy;
SomeObject* retrieved = nullptr; SomeObject* retrieved = nullptr;
EXPECT_TRUE(worklist.IsEmpty()); EXPECT_TRUE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
EXPECT_TRUE(worklist_view1.Push(&dummy)); EXPECT_TRUE(worklist_view1.Push(&dummy));
EXPECT_FALSE(worklist.IsEmpty()); EXPECT_FALSE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
EXPECT_FALSE(worklist_view2.Pop(&retrieved)); EXPECT_FALSE(worklist_view2.Pop(&retrieved));
EXPECT_EQ(nullptr, retrieved); EXPECT_EQ(nullptr, retrieved);
EXPECT_TRUE(worklist_view1.Pop(&retrieved)); EXPECT_TRUE(worklist_view1.Pop(&retrieved));
EXPECT_EQ(&dummy, retrieved); EXPECT_EQ(&dummy, retrieved);
EXPECT_TRUE(worklist.IsEmpty()); EXPECT_TRUE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
} }
TEST(WorkListTest, GlobalUpdateNull) { TEST(WorkListTest, GlobalUpdateNull) {
...@@ -168,6 +171,7 @@ TEST(WorkListTest, GlobalUpdateNull) { ...@@ -168,6 +171,7 @@ TEST(WorkListTest, GlobalUpdateNull) {
EXPECT_TRUE(worklist_view.Push(object)); EXPECT_TRUE(worklist_view.Push(object));
worklist.Update([](SomeObject* object, SomeObject** out) { return false; }); worklist.Update([](SomeObject* object, SomeObject** out) { return false; });
EXPECT_TRUE(worklist.IsEmpty()); EXPECT_TRUE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
} }
TEST(WorkListTest, GlobalUpdate) { TEST(WorkListTest, GlobalUpdate) {
...@@ -209,6 +213,7 @@ TEST(WorkListTest, FlushToGlobalPushSegment) { ...@@ -209,6 +213,7 @@ TEST(WorkListTest, FlushToGlobalPushSegment) {
objectA = reinterpret_cast<SomeObject*>(&objectA); objectA = reinterpret_cast<SomeObject*>(&objectA);
EXPECT_TRUE(worklist_view0.Push(objectA)); EXPECT_TRUE(worklist_view0.Push(objectA));
worklist.FlushToGlobal(0); worklist.FlushToGlobal(0);
EXPECT_EQ(1U, worklist.GlobalPoolSize());
EXPECT_TRUE(worklist_view1.Pop(&object)); EXPECT_TRUE(worklist_view1.Pop(&object));
} }
...@@ -223,6 +228,7 @@ TEST(WorkListTest, FlushToGlobalPopSegment) { ...@@ -223,6 +228,7 @@ TEST(WorkListTest, FlushToGlobalPopSegment) {
EXPECT_TRUE(worklist_view0.Push(objectA)); EXPECT_TRUE(worklist_view0.Push(objectA));
EXPECT_TRUE(worklist_view0.Pop(&object)); EXPECT_TRUE(worklist_view0.Pop(&object));
worklist.FlushToGlobal(0); worklist.FlushToGlobal(0);
EXPECT_EQ(1U, worklist.GlobalPoolSize());
EXPECT_TRUE(worklist_view1.Pop(&object)); EXPECT_TRUE(worklist_view1.Pop(&object));
} }
...@@ -235,8 +241,10 @@ TEST(WorkListTest, Clear) { ...@@ -235,8 +241,10 @@ TEST(WorkListTest, Clear) {
EXPECT_TRUE(worklist_view.Push(object)); EXPECT_TRUE(worklist_view.Push(object));
} }
EXPECT_TRUE(worklist_view.Push(object)); EXPECT_TRUE(worklist_view.Push(object));
EXPECT_EQ(1U, worklist.GlobalPoolSize());
worklist.Clear(); worklist.Clear();
EXPECT_TRUE(worklist.IsEmpty()); EXPECT_TRUE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
} }
TEST(WorkListTest, SingleSegmentSteal) { TEST(WorkListTest, SingleSegmentSteal) {
...@@ -252,6 +260,7 @@ TEST(WorkListTest, SingleSegmentSteal) { ...@@ -252,6 +260,7 @@ TEST(WorkListTest, SingleSegmentSteal) {
EXPECT_TRUE(worklist_view1.Push(nullptr)); EXPECT_TRUE(worklist_view1.Push(nullptr));
EXPECT_TRUE(worklist_view1.Pop(&retrieved)); EXPECT_TRUE(worklist_view1.Pop(&retrieved));
EXPECT_EQ(nullptr, retrieved); EXPECT_EQ(nullptr, retrieved);
EXPECT_EQ(1U, worklist.GlobalPoolSize());
// Stealing. // Stealing.
for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) {
EXPECT_TRUE(worklist_view2.Pop(&retrieved)); EXPECT_TRUE(worklist_view2.Pop(&retrieved));
...@@ -259,6 +268,7 @@ TEST(WorkListTest, SingleSegmentSteal) { ...@@ -259,6 +268,7 @@ TEST(WorkListTest, SingleSegmentSteal) {
EXPECT_FALSE(worklist_view1.Pop(&retrieved)); EXPECT_FALSE(worklist_view1.Pop(&retrieved));
} }
EXPECT_TRUE(worklist.IsEmpty()); EXPECT_TRUE(worklist.IsEmpty());
EXPECT_EQ(0U, worklist.GlobalPoolSize());
} }
TEST(WorkListTest, MultipleSegmentsStolen) { TEST(WorkListTest, MultipleSegmentsStolen) {
...@@ -280,11 +290,13 @@ TEST(WorkListTest, MultipleSegmentsStolen) { ...@@ -280,11 +290,13 @@ TEST(WorkListTest, MultipleSegmentsStolen) {
EXPECT_TRUE(worklist_view1.Push(&dummy3)); EXPECT_TRUE(worklist_view1.Push(&dummy3));
EXPECT_TRUE(worklist_view1.Pop(&retrieved)); EXPECT_TRUE(worklist_view1.Pop(&retrieved));
EXPECT_EQ(&dummy3, retrieved); EXPECT_EQ(&dummy3, retrieved);
EXPECT_EQ(2U, worklist.GlobalPoolSize());
// Stealing. // Stealing.
EXPECT_TRUE(worklist_view2.Pop(&retrieved)); EXPECT_TRUE(worklist_view2.Pop(&retrieved));
SomeObject* const expect_bag2 = retrieved; SomeObject* const expect_bag2 = retrieved;
EXPECT_TRUE(worklist_view3.Pop(&retrieved)); EXPECT_TRUE(worklist_view3.Pop(&retrieved));
SomeObject* const expect_bag3 = retrieved; SomeObject* const expect_bag3 = retrieved;
EXPECT_EQ(0U, worklist.GlobalPoolSize());
EXPECT_NE(expect_bag2, expect_bag3); EXPECT_NE(expect_bag2, expect_bag3);
EXPECT_TRUE(expect_bag2 == &dummy1 || expect_bag2 == &dummy2); EXPECT_TRUE(expect_bag2 == &dummy1 || expect_bag2 == &dummy2);
EXPECT_TRUE(expect_bag3 == &dummy1 || expect_bag3 == &dummy2); EXPECT_TRUE(expect_bag3 == &dummy1 || expect_bag3 == &dummy2);
...@@ -313,10 +325,13 @@ TEST(WorkListTest, MergeGlobalPool) { ...@@ -313,10 +325,13 @@ TEST(WorkListTest, MergeGlobalPool) {
EXPECT_TRUE(worklist_view1.Push(nullptr)); EXPECT_TRUE(worklist_view1.Push(nullptr));
EXPECT_TRUE(worklist_view1.Pop(&retrieved)); EXPECT_TRUE(worklist_view1.Pop(&retrieved));
EXPECT_EQ(nullptr, retrieved); EXPECT_EQ(nullptr, retrieved);
EXPECT_EQ(1U, worklist1.GlobalPoolSize());
// Merging global pool into a new Worklist. // Merging global pool into a new Worklist.
TestWorklist worklist2; TestWorklist worklist2;
TestWorklist::View worklist_view2(&worklist2, 0); TestWorklist::View worklist_view2(&worklist2, 0);
EXPECT_EQ(0U, worklist2.GlobalPoolSize());
worklist2.MergeGlobalPool(&worklist1); worklist2.MergeGlobalPool(&worklist1);
EXPECT_EQ(1U, worklist2.GlobalPoolSize());
EXPECT_FALSE(worklist2.IsEmpty()); EXPECT_FALSE(worklist2.IsEmpty());
for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) { for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) {
EXPECT_TRUE(worklist_view2.Pop(&retrieved)); EXPECT_TRUE(worklist_view2.Pop(&retrieved));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment