[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/+/2043273 Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org> Cr-Commit-Position: refs/heads/master@{#66175}
This commit is contained in:
parent
1d54a600ac
commit
4300eec7ca
@ -145,6 +145,9 @@ class Worklist {
|
||||
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.
|
||||
//
|
||||
// Assumes that no other tasks are running.
|
||||
@ -198,8 +201,7 @@ class Worklist {
|
||||
}
|
||||
|
||||
void MergeGlobalPool(Worklist* other) {
|
||||
auto pair = other->global_pool_.Extract();
|
||||
global_pool_.MergeList(pair.first, pair.second);
|
||||
global_pool_.Merge(&other->global_pool_);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -279,17 +281,23 @@ class Worklist {
|
||||
Segment* temp = top_;
|
||||
set_top(other.top_);
|
||||
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) {
|
||||
base::MutexGuard guard(&lock_);
|
||||
segment->set_next(top_);
|
||||
set_top(segment);
|
||||
size_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
V8_INLINE bool Pop(Segment** segment) {
|
||||
base::MutexGuard guard(&lock_);
|
||||
if (top_ != nullptr) {
|
||||
DCHECK_LT(0U, size_);
|
||||
size_.fetch_sub(1, std::memory_order_relaxed);
|
||||
*segment = top_;
|
||||
set_top(top_->next());
|
||||
return true;
|
||||
@ -301,8 +309,16 @@ class Worklist {
|
||||
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() {
|
||||
base::MutexGuard guard(&lock_);
|
||||
size_.store(0, std::memory_order_relaxed);
|
||||
Segment* current = top_;
|
||||
while (current != nullptr) {
|
||||
Segment* tmp = current;
|
||||
@ -318,9 +334,12 @@ class Worklist {
|
||||
base::MutexGuard guard(&lock_);
|
||||
Segment* prev = nullptr;
|
||||
Segment* current = top_;
|
||||
size_t num_deleted = 0;
|
||||
while (current != nullptr) {
|
||||
current->Update(callback);
|
||||
if (current->IsEmpty()) {
|
||||
DCHECK_LT(0U, size_);
|
||||
++num_deleted;
|
||||
if (prev == nullptr) {
|
||||
top_ = current->next();
|
||||
} else {
|
||||
@ -334,6 +353,7 @@ class Worklist {
|
||||
current = current->next();
|
||||
}
|
||||
}
|
||||
size_.fetch_sub(num_deleted, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// See Worklist::Iterate.
|
||||
@ -346,25 +366,28 @@ class Worklist {
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<Segment*, Segment*> Extract() {
|
||||
void Merge(GlobalPool* other) {
|
||||
Segment* top = nullptr;
|
||||
size_t other_size = 0;
|
||||
{
|
||||
base::MutexGuard guard(&lock_);
|
||||
if (top_ == nullptr) return std::make_pair(nullptr, nullptr);
|
||||
top = top_;
|
||||
set_top(nullptr);
|
||||
base::MutexGuard guard(&other->lock_);
|
||||
if (!other->top_) return;
|
||||
top = other->top_;
|
||||
other_size = other->size_.load(std::memory_order_relaxed);
|
||||
other->size_.store(0, std::memory_order_relaxed);
|
||||
other->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;
|
||||
// It's safe to iterate through these segments because the top was
|
||||
// extracted from |other|.
|
||||
Segment* end = top;
|
||||
while (end->next()) end = end->next();
|
||||
|
||||
{
|
||||
base::MutexGuard guard(&lock_);
|
||||
size_.fetch_add(other_size, std::memory_order_relaxed);
|
||||
end->set_next(top_);
|
||||
set_top(start);
|
||||
set_top(top);
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,6 +398,7 @@ class Worklist {
|
||||
|
||||
base::Mutex lock_;
|
||||
Segment* top_;
|
||||
std::atomic<size_t> size_{0};
|
||||
};
|
||||
|
||||
V8_INLINE Segment*& private_push_segment(int task_id) {
|
||||
|
@ -148,13 +148,16 @@ TEST(WorkListTest, LocalPushStaysPrivate) {
|
||||
SomeObject dummy;
|
||||
SomeObject* retrieved = nullptr;
|
||||
EXPECT_TRUE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
EXPECT_TRUE(worklist_view1.Push(&dummy));
|
||||
EXPECT_FALSE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
EXPECT_FALSE(worklist_view2.Pop(&retrieved));
|
||||
EXPECT_EQ(nullptr, retrieved);
|
||||
EXPECT_TRUE(worklist_view1.Pop(&retrieved));
|
||||
EXPECT_EQ(&dummy, retrieved);
|
||||
EXPECT_TRUE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
}
|
||||
|
||||
TEST(WorkListTest, GlobalUpdateNull) {
|
||||
@ -168,6 +171,7 @@ TEST(WorkListTest, GlobalUpdateNull) {
|
||||
EXPECT_TRUE(worklist_view.Push(object));
|
||||
worklist.Update([](SomeObject* object, SomeObject** out) { return false; });
|
||||
EXPECT_TRUE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
}
|
||||
|
||||
TEST(WorkListTest, GlobalUpdate) {
|
||||
@ -209,6 +213,7 @@ TEST(WorkListTest, FlushToGlobalPushSegment) {
|
||||
objectA = reinterpret_cast<SomeObject*>(&objectA);
|
||||
EXPECT_TRUE(worklist_view0.Push(objectA));
|
||||
worklist.FlushToGlobal(0);
|
||||
EXPECT_EQ(1U, worklist.GlobalPoolSize());
|
||||
EXPECT_TRUE(worklist_view1.Pop(&object));
|
||||
}
|
||||
|
||||
@ -223,6 +228,7 @@ TEST(WorkListTest, FlushToGlobalPopSegment) {
|
||||
EXPECT_TRUE(worklist_view0.Push(objectA));
|
||||
EXPECT_TRUE(worklist_view0.Pop(&object));
|
||||
worklist.FlushToGlobal(0);
|
||||
EXPECT_EQ(1U, worklist.GlobalPoolSize());
|
||||
EXPECT_TRUE(worklist_view1.Pop(&object));
|
||||
}
|
||||
|
||||
@ -235,8 +241,10 @@ TEST(WorkListTest, Clear) {
|
||||
EXPECT_TRUE(worklist_view.Push(object));
|
||||
}
|
||||
EXPECT_TRUE(worklist_view.Push(object));
|
||||
EXPECT_EQ(1U, worklist.GlobalPoolSize());
|
||||
worklist.Clear();
|
||||
EXPECT_TRUE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
}
|
||||
|
||||
TEST(WorkListTest, SingleSegmentSteal) {
|
||||
@ -252,6 +260,7 @@ TEST(WorkListTest, SingleSegmentSteal) {
|
||||
EXPECT_TRUE(worklist_view1.Push(nullptr));
|
||||
EXPECT_TRUE(worklist_view1.Pop(&retrieved));
|
||||
EXPECT_EQ(nullptr, retrieved);
|
||||
EXPECT_EQ(1U, worklist.GlobalPoolSize());
|
||||
// Stealing.
|
||||
for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) {
|
||||
EXPECT_TRUE(worklist_view2.Pop(&retrieved));
|
||||
@ -259,6 +268,7 @@ TEST(WorkListTest, SingleSegmentSteal) {
|
||||
EXPECT_FALSE(worklist_view1.Pop(&retrieved));
|
||||
}
|
||||
EXPECT_TRUE(worklist.IsEmpty());
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
}
|
||||
|
||||
TEST(WorkListTest, MultipleSegmentsStolen) {
|
||||
@ -280,11 +290,13 @@ TEST(WorkListTest, MultipleSegmentsStolen) {
|
||||
EXPECT_TRUE(worklist_view1.Push(&dummy3));
|
||||
EXPECT_TRUE(worklist_view1.Pop(&retrieved));
|
||||
EXPECT_EQ(&dummy3, retrieved);
|
||||
EXPECT_EQ(2U, worklist.GlobalPoolSize());
|
||||
// Stealing.
|
||||
EXPECT_TRUE(worklist_view2.Pop(&retrieved));
|
||||
SomeObject* const expect_bag2 = retrieved;
|
||||
EXPECT_TRUE(worklist_view3.Pop(&retrieved));
|
||||
SomeObject* const expect_bag3 = retrieved;
|
||||
EXPECT_EQ(0U, worklist.GlobalPoolSize());
|
||||
EXPECT_NE(expect_bag2, expect_bag3);
|
||||
EXPECT_TRUE(expect_bag2 == &dummy1 || expect_bag2 == &dummy2);
|
||||
EXPECT_TRUE(expect_bag3 == &dummy1 || expect_bag3 == &dummy2);
|
||||
@ -313,10 +325,13 @@ TEST(WorkListTest, MergeGlobalPool) {
|
||||
EXPECT_TRUE(worklist_view1.Push(nullptr));
|
||||
EXPECT_TRUE(worklist_view1.Pop(&retrieved));
|
||||
EXPECT_EQ(nullptr, retrieved);
|
||||
EXPECT_EQ(1U, worklist1.GlobalPoolSize());
|
||||
// Merging global pool into a new Worklist.
|
||||
TestWorklist worklist2;
|
||||
TestWorklist::View worklist_view2(&worklist2, 0);
|
||||
EXPECT_EQ(0U, worklist2.GlobalPoolSize());
|
||||
worklist2.MergeGlobalPool(&worklist1);
|
||||
EXPECT_EQ(1U, worklist2.GlobalPoolSize());
|
||||
EXPECT_FALSE(worklist2.IsEmpty());
|
||||
for (size_t i = 0; i < TestWorklist::kSegmentCapacity; i++) {
|
||||
EXPECT_TRUE(worklist_view2.Pop(&retrieved));
|
||||
|
Loading…
Reference in New Issue
Block a user