Index: runtime/vm/store_buffer.cc |
diff --git a/runtime/vm/store_buffer.cc b/runtime/vm/store_buffer.cc |
index 99a1371a909c10a51ca220d247d10e178617b70e..a590c28fb7627d806ad52a8468a427e7f6583878 100644 |
--- a/runtime/vm/store_buffer.cc |
+++ b/runtime/vm/store_buffer.cc |
@@ -16,6 +16,16 @@ DEFINE_LEAF_RUNTIME_ENTRY(void, StoreBufferBlockProcess, 1, Thread* thread) { |
END_LEAF_RUNTIME_ENTRY |
+StoreBuffer::List* StoreBuffer::global_empty_ = NULL; |
+Mutex* StoreBuffer::global_mutex_ = NULL; |
+ |
+ |
+void StoreBuffer::InitOnce() { |
+ global_empty_ = new List(); |
+ global_mutex_ = new Mutex(); |
+} |
+ |
+ |
StoreBuffer::StoreBuffer() : mutex_(new Mutex()) { |
} |
@@ -27,13 +37,21 @@ StoreBuffer::~StoreBuffer() { |
void StoreBuffer::Reset() { |
- MutexLocker ml(mutex_); |
- // TODO(koda): Reuse and share empty blocks between isolates. |
- while (!full_.IsEmpty()) { |
- delete full_.Pop(); |
- } |
- while (!partial_.IsEmpty()) { |
- delete partial_.Pop(); |
+ MutexLocker local_mutex_locker(mutex_); |
+ { |
+ // Empty all blocks and move them to the global cache. |
+ MutexLocker global_mutex_locker(global_mutex_); |
+ while (!full_.IsEmpty()) { |
+ StoreBufferBlock* block = full_.Pop(); |
+ block->Reset(); |
+ global_empty_->Push(block); |
+ } |
+ while (!partial_.IsEmpty()) { |
+ StoreBufferBlock* block = partial_.Pop(); |
+ block->Reset(); |
+ global_empty_->Push(block); |
+ } |
+ TrimGlobalEmpty(); |
} |
} |
@@ -48,23 +66,43 @@ StoreBufferBlock* StoreBuffer::Blocks() { |
void StoreBuffer::PushBlock(StoreBufferBlock* block, bool check_threshold) { |
- MutexLocker ml(mutex_); |
- List* list = block->IsFull() ? &full_ : &partial_; |
- list->Push(block); |
+ ASSERT(block->next() == NULL); // Should be just a single block. |
+ if (block->IsFull()) { |
+ MutexLocker ml(mutex_); |
+ full_.Push(block); |
+ } else if (block->IsEmpty()) { |
+ MutexLocker ml(global_mutex_); |
+ global_empty_->Push(block); |
+ TrimGlobalEmpty(); |
+ } else { |
+ MutexLocker ml(mutex_); |
+ partial_.Push(block); |
+ } |
if (check_threshold) { |
- CheckThreshold(); |
+ MutexLocker ml(mutex_); |
+ CheckThresholdNonEmpty(); |
} |
} |
StoreBufferBlock* StoreBuffer::PopBlock() { |
- MutexLocker ml(mutex_); |
- return (!partial_.IsEmpty()) ? partial_.Pop() : PopEmptyBlock(); |
+ { |
+ MutexLocker ml(mutex_); |
+ if (!partial_.IsEmpty()) { |
+ return partial_.Pop(); |
+ } |
+ } |
+ return PopEmptyBlock(); |
} |
StoreBufferBlock* StoreBuffer::PopEmptyBlock() { |
- // TODO(koda): Reuse and share empty blocks between isolates. |
+ { |
+ MutexLocker ml(global_mutex_); |
+ if (!global_empty_->IsEmpty()) { |
+ global_empty_->Pop(); |
+ } |
+ } |
return new StoreBufferBlock(); |
} |
@@ -94,18 +132,30 @@ StoreBufferBlock* StoreBuffer::List::PopAll() { |
void StoreBuffer::List::Push(StoreBufferBlock* block) { |
+ ASSERT(block->next_ == NULL); |
block->next_ = head_; |
head_ = block; |
++length_; |
} |
-void StoreBuffer::CheckThreshold() { |
- // Schedule an interrupt if we have run over the max number of |
- // StoreBufferBlocks. |
- // TODO(koda): Pass threshold and callback in constructor. Cap total? |
- if (full_.length() > 100) { |
- Isolate::Current()->ScheduleInterrupts(Isolate::kStoreBufferInterrupt); |
+void StoreBuffer::CheckThresholdNonEmpty() { |
+ DEBUG_ASSERT(mutex_->IsOwnedByCurrentThread()); |
+ if (full_.length() + partial_.length() > kMaxNonEmpty) { |
+ Isolate* isolate = Isolate::Current(); |
+ // Sanity check: it makes no sense to schedule the GC in another isolate. |
+ // (If Isolate ever gets multiple store buffers, we should avoid this |
+ // coupling by passing in an explicit callback+parameter at construction.) |
+ ASSERT(isolate->store_buffer() == this); |
+ isolate->ScheduleInterrupts(Isolate::kStoreBufferInterrupt); |
+ } |
+} |
+ |
+ |
+void StoreBuffer::TrimGlobalEmpty() { |
+ DEBUG_ASSERT(global_mutex_->IsOwnedByCurrentThread()); |
+ while (global_empty_->length() > kMaxGlobalEmpty) { |
+ delete global_empty_->Pop(); |
} |
} |