Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1260)

Unified Diff: runtime/vm/gc_marker.cc

Issue 1351453008: Parallel marking. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Add TODO to remove busy wait Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/gc_marker.h ('k') | runtime/vm/heap_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/vm/gc_marker.cc
diff --git a/runtime/vm/gc_marker.cc b/runtime/vm/gc_marker.cc
index 0df41177730edde525bf83f910d73ef19df78ccd..fe0a30da4764fb4176e5df573489d9c8745fc77c 100644
--- a/runtime/vm/gc_marker.cc
+++ b/runtime/vm/gc_marker.cc
@@ -23,9 +23,11 @@
namespace dart {
-DEFINE_FLAG(int, marker_tasks, 1,
+DEFINE_FLAG(int, marker_tasks, 2,
"The number of tasks to spawn during old gen GC marking (0 means "
"perform all marking on main thread).");
+DEFINE_FLAG(bool, log_marker_tasks, false,
+ "Log debugging information for old gen GC marking tasks.");
class DelaySet {
private:
@@ -36,24 +38,30 @@ class DelaySet {
DelaySet() : mutex_(new Mutex()) {}
~DelaySet() { delete mutex_; }
- // Returns 'true' if this inserted a new key (not just added a value).
- bool Insert(RawWeakProperty* raw_weak) {
+ // Atomically inserts raw_weak if its key is white, so that any future call to
+ // VisitValuesForKey is guaranteed to include its value. Returns true on
+ // success, and false if the key is not white.
Ivan Posva 2015/10/08 19:56:12 Please also note the precondition that this thread
koda 2015/10/08 21:32:08 Done.
+ bool InsertIfWhite(RawWeakProperty* raw_weak) {
MutexLocker ml(mutex_);
RawObject* raw_key = raw_weak->ptr()->key_;
- bool new_key = (delay_set_.find(raw_key) == delay_set_.end());
+ if (raw_key->IsMarked()) return false;
+ // The key was white *after* acquiring the lock. Thus any future call to
+ // VisitValuesForKey is guaranteed to include the entry inserted below.
delay_set_.insert(std::make_pair(raw_key, raw_weak));
- return new_key;
+ return true;
}
void ClearReferences() {
MutexLocker ml(mutex_);
for (Map::iterator it = delay_set_.begin(); it != delay_set_.end(); ++it) {
+ ASSERT(!it->first->IsMarked());
WeakProperty::Clear(it->second);
}
}
- // Visit all values with a key equal to raw_obj.
+ // Visit all values with a key equal to raw_obj, which must already be marked.
void VisitValuesForKey(RawObject* raw_obj, ObjectPointerVisitor* visitor) {
+ ASSERT(raw_obj->IsMarked());
// Extract the range into a temporary vector to iterate over it
// while delay_set_ may be modified.
std::vector<MapEntry> temp_copy;
@@ -140,9 +148,67 @@ class SkippedCodeFunctions : public ZoneAllocated {
};
-class MarkingVisitor : public ObjectPointerVisitor {
+class WorkList : public ValueObject {
Ivan Posva 2015/10/08 19:56:12 Thanks! Maybe rename to MarkerWorkList?
koda 2015/10/08 21:32:07 Done.
public:
- MarkingVisitor(Isolate* isolate,
+ explicit WorkList(MarkingStack* marking_stack)
+ : marking_stack_(marking_stack) {
+ work_ = marking_stack_->PopEmptyBlock();
+ }
+
+ ~WorkList() {
+ ASSERT(work_ == NULL);
+ ASSERT(marking_stack_ == NULL);
+ }
+
+ // Returns NULL if no more work was found.
+ RawObject* Pop() {
+ ASSERT(work_ != NULL);
+ if (work_->IsEmpty()) {
+ // TODO(koda): Track over/underflow events and use in heuristics to
+ // distribute work and prevent degenerate flip-flopping.
+ MarkingStack::Block* new_work = marking_stack_->PopNonEmptyBlock();
+ if (new_work == NULL) {
+ return NULL;
+ }
+ marking_stack_->PushBlock(work_);
+ work_ = new_work;
+ }
+ return work_->Pop();
+ }
+
+ void Push(RawObject* raw_obj) {
+ if (work_->IsFull()) {
+ // TODO(koda): Track over/underflow events and use in heuristics to
+ // distribute work and prevent degenerate flip-flopping.
+ marking_stack_->PushBlock(work_);
+ work_ = marking_stack_->PopEmptyBlock();
+ }
+ work_->Push(raw_obj);
+ }
+
+ void AbandonWork() {
+ marking_stack_->PushBlock(work_);
+ work_ = marking_stack_->PopEmptyBlock();
+ }
+
+ void Finalize() {
+ ASSERT(work_->IsEmpty());
+ marking_stack_->PushBlock(work_);
+ work_ = NULL;
+ // Fail fast on attempts to mark after finalizing.
+ marking_stack_ = NULL;
+ }
+
+ private:
+ MarkingStack::Block* work_;
+ MarkingStack* marking_stack_;
+};
+
+
+template<bool sync>
+class MarkingVisitorBase : public ObjectPointerVisitor {
+ public:
+ MarkingVisitorBase(Isolate* isolate,
Heap* heap,
PageSpace* page_space,
MarkingStack* marking_stack,
@@ -190,9 +256,6 @@ class MarkingVisitor : public ObjectPointerVisitor {
do {
VisitingOldObject(raw_obj);
const intptr_t class_id = raw_obj->GetClassId();
- // Currently, classes are considered roots (see issue 18284), so at this
- // point, they should all be marked.
- ASSERT(isolate()->class_table()->At(class_id)->IsMarked());
if (class_id != kWeakPropertyCid) {
marked_bytes_ += raw_obj->VisitPointers(this);
} else {
@@ -221,32 +284,35 @@ class MarkingVisitor : public ObjectPointerVisitor {
skipped_code_functions_->Add(func);
}
- // Returns the mark bit. Sets the watch bit if unmarked. (The prior value of
- // the watched bit is returned in 'watched_before' for validation purposes.)
- // TODO(koda): When synchronizing header bits, this goes in a single CAS loop.
- static bool EnsureWatchedIfWhite(RawObject* obj, bool* watched_before) {
- if (obj->IsMarked()) {
- return false;
- }
- if (!obj->IsWatched()) {
- *watched_before = false;
- obj->SetWatchedBitUnsynchronized();
- } else {
- *watched_before = true;
+ // If unmarked, sets the watch bit and returns true.
+ // If marked, does nothing and returns false.
+ static bool EnsureWatchedIfWhite(RawObject* obj) {
+ if (!sync) {
+ if (obj->IsMarked()) return false;
+ if (!obj->IsWatched()) obj->SetWatchedBitUnsynchronized();
+ return true;
}
+ uword tags = obj->ptr()->tags_;
+ uword old_tags;
+ do {
+ old_tags = tags;
+ if (RawObject::MarkBit::decode(tags)) return false;
+ if (RawObject::WatchedBit::decode(tags)) return true;
+ uword new_tags = RawObject::WatchedBit::update(true, old_tags);
+ tags = AtomicOperations::CompareAndSwapWord(
+ &obj->ptr()->tags_, old_tags, new_tags);
+ } while (tags != old_tags);
return true;
}
void ProcessWeakProperty(RawWeakProperty* raw_weak) {
// The fate of the weak property is determined by its key.
RawObject* raw_key = raw_weak->ptr()->key_;
- bool watched_before = false;
if (raw_key->IsHeapObject() &&
raw_key->IsOldObject() &&
- EnsureWatchedIfWhite(raw_key, &watched_before)) {
- // Key is white. Delay the weak property.
- bool new_key = delay_set_->Insert(raw_weak);
- ASSERT(new_key == !watched_before);
+ EnsureWatchedIfWhite(raw_key) &&
+ delay_set_->InsertIfWhite(raw_weak)) {
+ // Key was white. Delayed the weak property.
} else {
// Key is gray or black. Make the weak property black.
raw_weak->VisitPointers(this);
@@ -266,68 +332,23 @@ class MarkingVisitor : public ObjectPointerVisitor {
visiting_old_object_ = obj;
}
- private:
- class WorkList : public ValueObject {
- public:
- explicit WorkList(MarkingStack* marking_stack)
- : marking_stack_(marking_stack) {
- work_ = marking_stack_->PopEmptyBlock();
- }
-
- ~WorkList() {
- ASSERT(work_ == NULL);
- ASSERT(marking_stack_ == NULL);
- }
-
- // Returns NULL if no more work was found.
- RawObject* Pop() {
- ASSERT(work_ != NULL);
- if (work_->IsEmpty()) {
- // TODO(koda): Track over/underflow events and use in heuristics to
- // distribute work and prevent degenerate flip-flopping.
- MarkingStack::Block* new_work = marking_stack_->PopNonEmptyBlock();
- if (new_work == NULL) {
- return NULL;
- }
- marking_stack_->PushBlock(work_);
- work_ = new_work;
- }
- return work_->Pop();
- }
-
- void Push(RawObject* raw_obj) {
- if (work_->IsFull()) {
- // TODO(koda): Track over/underflow events and use in heuristics to
- // distribute work and prevent degenerate flip-flopping.
- marking_stack_->PushBlock(work_);
- work_ = marking_stack_->PopEmptyBlock();
- }
- work_->Push(raw_obj);
- }
-
- void Finalize() {
- ASSERT(work_->IsEmpty());
- marking_stack_->PushBlock(work_);
- work_ = NULL;
- // Fail fast on attempts to mark after finalizing.
- marking_stack_ = NULL;
- }
-
- private:
- MarkingStack::Block* work_;
- MarkingStack* marking_stack_;
- };
+ void AbandonWork() {
Ivan Posva 2015/10/08 19:56:12 Please document where this is used.
koda 2015/10/08 21:32:07 Removed.
+ work_list_.AbandonWork();
+ }
- void MarkAndPush(RawObject* raw_obj) {
+ private:
+ void PushMarked(RawObject* raw_obj) {
ASSERT(raw_obj->IsHeapObject());
ASSERT((FLAG_verify_before_gc || FLAG_verify_before_gc) ?
page_space_->Contains(RawObject::ToAddr(raw_obj)) :
true);
- // Mark the object and push it on the marking stack.
- ASSERT(!raw_obj->IsMarked());
+ // Push the marked object on the marking stack.
+ ASSERT(raw_obj->IsMarked());
const bool is_watched = raw_obj->IsWatched();
- raw_obj->SetMarkBitUnsynchronized();
+ // We acquired the mark bit => no other task is modifying the header.
+ // TODO(koda): For concurrent mutator, this needs synchronization. Consider
+ // clearing these bits already in the CAS for the mark bit.
raw_obj->ClearRememberedBitUnsynchronized();
raw_obj->ClearWatchedBitUnsynchronized();
if (is_watched) {
@@ -336,6 +357,15 @@ class MarkingVisitor : public ObjectPointerVisitor {
work_list_.Push(raw_obj);
}
+ static bool TryAcquireMarkBit(RawObject* raw_obj) {
+ if (!sync) {
+ if (raw_obj->IsMarked()) return false;
+ raw_obj->SetMarkBitUnsynchronized();
+ return true;
+ }
+ return raw_obj->TryAcquireMarkBit();
+ }
+
void MarkObject(RawObject* raw_obj, RawObject** p) {
// Fast exit if the raw object is a Smi.
if (!raw_obj->IsHeapObject()) {
@@ -347,15 +377,19 @@ class MarkingVisitor : public ObjectPointerVisitor {
return;
}
- // Skip over new objects, but verify consistency of heap while at it.
+ // TODO(koda): Investigate performance impact of alternative branching:
+ // if (smi or new) <-- can be done as single compare + conditional jump
+ // if (smi) return;
+ // else ...
+ // if (marked) return;
+ // ...
if (raw_obj->IsNewObject()) {
- // TODO(iposva): Add consistency check.
- if ((visiting_old_object_ != NULL) &&
- !visiting_old_object_->IsRemembered()) {
- ASSERT(p != NULL);
- visiting_old_object_->SetRememberedBitUnsynchronized();
- thread_->StoreBufferAddObjectGC(visiting_old_object_);
- }
+ ProcessNewSpaceObject(raw_obj, p);
+ return;
+ }
+
+ if (!TryAcquireMarkBit(raw_obj)) {
+ // Already marked.
return;
}
if (RawObject::IsVariableSizeClassId(raw_obj->GetClassId())) {
@@ -364,7 +398,32 @@ class MarkingVisitor : public ObjectPointerVisitor {
UpdateLiveOld(raw_obj->GetClassId(), 0);
}
- MarkAndPush(raw_obj);
+ PushMarked(raw_obj);
+ }
+
+ static bool TryAcquireRememberedBit(RawObject* raw_obj) {
+ if (!sync) {
+ if (raw_obj->IsRemembered()) return false;
+ raw_obj->SetRememberedBitUnsynchronized();
+ return true;
+ }
+ return raw_obj->TryAcquireRememberedBit();
+ }
+
+ void ProcessNewSpaceObject(RawObject* raw_obj, RawObject** p) {
+ // TODO(iposva): Add consistency check.
+ if ((visiting_old_object_ != NULL) &&
+ TryAcquireRememberedBit(visiting_old_object_)) {
+ // NOTE: We pass in the pointer to the address we are visiting
+ // allows us to get a distance from the object start. At some
+ // point we might want to store exact addresses in store buffers
+ // for locations far enough from the header, so that we do not
+ // need to walk big objects only to find the single new
+ // reference in the last word during scavenge. This doesn't seem
+ // to be a problem though currently.
+ ASSERT(p != NULL);
+ thread_->StoreBufferAddObjectGC(visiting_old_object_);
+ }
}
void UpdateLiveOld(intptr_t class_id, intptr_t size) {
@@ -386,10 +445,14 @@ class MarkingVisitor : public ObjectPointerVisitor {
SkippedCodeFunctions* skipped_code_functions_;
uintptr_t marked_bytes_;
- DISALLOW_IMPLICIT_CONSTRUCTORS(MarkingVisitor);
+ DISALLOW_IMPLICIT_CONSTRUCTORS(MarkingVisitorBase);
};
+typedef MarkingVisitorBase<false> UnsyncMarkingVisitor;
+typedef MarkingVisitorBase<true> SyncMarkingVisitor;
+
+
static bool IsUnreachable(const RawObject* raw_obj) {
if (!raw_obj->IsHeapObject()) {
return false;
@@ -442,11 +505,20 @@ void GCMarker::Epilogue(Isolate* isolate, bool invoke_api_callbacks) {
void GCMarker::IterateRoots(Isolate* isolate,
ObjectPointerVisitor* visitor,
- bool visit_prologue_weak_persistent_handles) {
- isolate->VisitObjectPointers(visitor,
- visit_prologue_weak_persistent_handles,
- StackFrameIterator::kDontValidateFrames);
- heap_->new_space()->VisitObjectPointers(visitor);
+ bool visit_prologue_weak_persistent_handles,
+ intptr_t slice_index, intptr_t num_slices) {
+ ASSERT(0 <= slice_index && slice_index < num_slices);
+ if (slice_index == 0 || num_slices <= 1) {
Ivan Posva 2015/10/08 19:56:12 ()
koda 2015/10/08 21:32:07 Done x2.
+ isolate->VisitObjectPointers(visitor,
+ visit_prologue_weak_persistent_handles,
+ StackFrameIterator::kDontValidateFrames);
+ }
+ if (slice_index == 1 || num_slices <= 1) {
+ heap_->new_space()->VisitObjectPointers(visitor);
+ }
+
+ // For now, we just distinguish two parts of the root set, so any remaining
+ // slices are empty.
}
@@ -460,8 +532,9 @@ void GCMarker::IterateWeakRoots(Isolate* isolate,
}
+template<class MarkingVisitorType>
void GCMarker::IterateWeakReferences(Isolate* isolate,
- MarkingVisitor* visitor) {
+ MarkingVisitorType* visitor) {
ApiState* state = isolate->api_state();
ASSERT(state != NULL);
while (true) {
@@ -576,7 +649,10 @@ class MarkTask : public ThreadPool::Task {
DelaySet* delay_set,
ThreadBarrier* barrier,
bool collect_code,
- bool visit_prologue_weak_persistent_handles)
+ bool visit_prologue_weak_persistent_handles,
+ intptr_t task_index,
+ intptr_t num_tasks,
+ uintptr_t* num_busy)
: marker_(marker),
isolate_(isolate),
heap_(heap),
@@ -586,7 +662,10 @@ class MarkTask : public ThreadPool::Task {
barrier_(barrier),
collect_code_(collect_code),
visit_prologue_weak_persistent_handles_(
- visit_prologue_weak_persistent_handles) {
+ visit_prologue_weak_persistent_handles),
+ task_index_(task_index),
+ num_tasks_(num_tasks),
+ num_busy_(num_busy) {
}
virtual void Run() {
@@ -596,20 +675,47 @@ class MarkTask : public ThreadPool::Task {
Zone* zone = stack_zone.GetZone();
SkippedCodeFunctions* skipped_code_functions =
collect_code_ ? new(zone) SkippedCodeFunctions() : NULL;
- MarkingVisitor visitor(isolate_, heap_, page_space_, marking_stack_,
- delay_set_, skipped_code_functions);
- // Phase 1: Populate and drain marking stack in task.
- // TODO(koda): Split root iteration work among multiple tasks.
+ SyncMarkingVisitor visitor(isolate_, heap_, page_space_, marking_stack_,
+ delay_set_, skipped_code_functions);
+ // Phase 1: Iterate over roots and drain marking stack in tasks.
marker_->IterateRoots(isolate_, &visitor,
- visit_prologue_weak_persistent_handles_);
- visitor.DrainMarkingStack();
+ visit_prologue_weak_persistent_handles_,
+ task_index_, num_tasks_);
+ do {
+ visitor.DrainMarkingStack();
+
+ // I can't find more work right now. If no other task is busy,
+ // then there will never be more work (NB: 1 is *before* decrement).
+ if (AtomicOperations::FetchAndDecrement(num_busy_) == 1) break;
+
+ // Wait for some work to appear.
+ // TODO(iposva): Replace busy-waiting with a solution using Monitor,
+ // and redraw the boundaries between stack/visitor/task as needed.
+ while (marking_stack_->IsEmpty() &&
+ AtomicOperations::LoadRelaxed(num_busy_) > 0) {
+ }
+
+ // If no tasks are busy, there will never be more work.
+ if (AtomicOperations::LoadRelaxed(num_busy_) == 0) break;
+
+ // I saw some work; get busy and compete for it.
+ AtomicOperations::FetchAndIncrement(num_busy_);
+ } while (true);
+ ASSERT(AtomicOperations::LoadRelaxed(num_busy_) == 0);
barrier_->Sync();
+
// Phase 2: Weak processing and follow-up marking on main thread.
barrier_->Sync();
+
// Phase 3: Finalize results from all markers (detach code, etc.).
+ if (FLAG_log_marker_tasks) {
+ THR_Print("Task %" Pd " marked %" Pd " bytes.\n",
+ task_index_, visitor.marked_bytes());
+ }
marker_->FinalizeResultsFrom(&visitor);
}
Thread::ExitIsolateAsHelper(true);
+
// This task is done. Notify the original thread.
barrier_->Exit();
}
@@ -624,12 +730,16 @@ class MarkTask : public ThreadPool::Task {
ThreadBarrier* barrier_;
bool collect_code_;
bool visit_prologue_weak_persistent_handles_;
+ const intptr_t task_index_;
+ const intptr_t num_tasks_;
+ uintptr_t* num_busy_;
DISALLOW_COPY_AND_ASSIGN(MarkTask);
};
-void GCMarker::FinalizeResultsFrom(MarkingVisitor* visitor) {
+template<class MarkingVisitorType>
+void GCMarker::FinalizeResultsFrom(MarkingVisitorType* visitor) {
{
MutexLocker ml(&stats_mutex_);
marked_bytes_ += visitor->marked_bytes();
@@ -667,9 +777,10 @@ void GCMarker::MarkObjects(Isolate* isolate,
// Mark everything on main thread.
SkippedCodeFunctions* skipped_code_functions =
collect_code ? new(zone) SkippedCodeFunctions() : NULL;
- MarkingVisitor mark(isolate, heap_, page_space, &marking_stack,
- &delay_set, skipped_code_functions);
- IterateRoots(isolate, &mark, visit_prologue_weak_persistent_handles);
+ UnsyncMarkingVisitor mark(isolate, heap_, page_space, &marking_stack,
+ &delay_set, skipped_code_functions);
+ IterateRoots(isolate, &mark, visit_prologue_weak_persistent_handles,
+ 0, 1);
mark.DrainMarkingStack();
IterateWeakReferences(isolate, &mark);
MarkingWeakVisitor mark_weak;
@@ -678,32 +789,37 @@ void GCMarker::MarkObjects(Isolate* isolate,
// All marking done; detach code, etc.
FinalizeResultsFrom(&mark);
} else {
- if (num_tasks > 1) {
- // TODO(koda): Support multiple:
- // 1. non-concurrent tasks, after splitting root iteration work, then
- // 2. concurrent tasks, after synchronizing headers.
- FATAL("Multiple marking tasks not yet supported");
+ ThreadBarrier barrier(num_tasks + 1);
+ // Used to coordinate draining among tasks; all start out as 'busy'.
+ uintptr_t num_busy = num_tasks;
+ // Phase 1: Iterate over roots and drain marking stack in tasks.
+ for (intptr_t i = 0; i < num_tasks; ++i) {
+ MarkTask* mark_task =
+ new MarkTask(this, isolate, heap_, page_space, &marking_stack,
+ &delay_set, &barrier, collect_code,
+ visit_prologue_weak_persistent_handles,
+ i, num_tasks, &num_busy);
+ ThreadPool* pool = Dart::thread_pool();
+ pool->Run(mark_task);
}
- ThreadBarrier barrier(num_tasks + 1); // +1 for the main thread.
- // Phase 1: Populate and drain marking stack in task.
- MarkTask* mark_task =
- new MarkTask(this, isolate, heap_, page_space, &marking_stack,
- &delay_set, &barrier, collect_code,
- visit_prologue_weak_persistent_handles);
- ThreadPool* pool = Dart::thread_pool();
- pool->Run(mark_task);
barrier.Sync();
+
// Phase 2: Weak processing and follow-up marking on main thread.
SkippedCodeFunctions* skipped_code_functions =
collect_code ? new(zone) SkippedCodeFunctions() : NULL;
- MarkingVisitor mark(isolate, heap_, page_space, &marking_stack,
- &delay_set, skipped_code_functions);
+ SyncMarkingVisitor mark(isolate, heap_, page_space, &marking_stack,
+ &delay_set, skipped_code_functions);
IterateWeakReferences(isolate, &mark);
MarkingWeakVisitor mark_weak;
IterateWeakRoots(isolate, &mark_weak,
!visit_prologue_weak_persistent_handles);
barrier.Sync();
+
// Phase 3: Finalize results from all markers (detach code, etc.).
+ if (FLAG_log_marker_tasks) {
+ THR_Print("Main thread marked %" Pd " bytes.\n",
+ mark.marked_bytes());
+ }
FinalizeResultsFrom(&mark);
barrier.Exit();
}
« no previous file with comments | « runtime/vm/gc_marker.h ('k') | runtime/vm/heap_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698