Proof-of-concept pretenuring of some strings:

- Add bump-pointer allocated block in page space.
- Pretenure num.toString whenever >98% of strings are being promoted.
- Fix deadlock in freelist printing.

Review URL: https://codereview.chromium.org//511963007

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@39688 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
koda@google.com
2014-08-29 03:54:12 +00:00
parent ac6b6f8fe4
commit 0844cbcfbe
7 changed files with 189 additions and 34 deletions
+3 -1
View File
@@ -11,7 +11,9 @@ namespace dart {
DEFINE_NATIVE_ENTRY(Num_toString, 1) {
const Number& number = Number::CheckedHandle(arguments->NativeArgAt(0));
return number.ToString(Heap::kNew);
Heap::Space space = isolate->heap()->ShouldPretenure(kOneByteStringCid) ?
Heap::kPretenured : Heap::kNew;
return number.ToString(space);
}
} // namespace dart
+25 -3
View File
@@ -235,8 +235,8 @@ FreeListElement* FreeList::DequeueElement(intptr_t index) {
}
intptr_t FreeList::Length(int index) const {
MutexLocker ml(mutex_);
intptr_t FreeList::LengthLocked(int index) const {
DEBUG_ASSERT(mutex_->Owner() == Isolate::Current());
ASSERT(index >= 0);
ASSERT(index < kNumLists);
intptr_t result = 0;
@@ -258,7 +258,7 @@ void FreeList::PrintSmall() const {
continue;
}
small_sizes += 1;
intptr_t list_length = Length(i);
intptr_t list_length = LengthLocked(i);
small_objects += list_length;
intptr_t list_bytes = list_length * i * kObjectAlignment;
small_bytes += list_bytes;
@@ -341,4 +341,26 @@ void FreeList::SplitElementAfterAndEnqueue(FreeListElement* element,
}
}
FreeListElement* FreeList::TryAllocateLarge(intptr_t minimum_size) {
MutexLocker ml(mutex_);
FreeListElement* previous = NULL;
FreeListElement* current = free_lists_[kNumLists];
// TODO(koda): Find largest.
while (current != NULL) {
FreeListElement* next = current->next();
if (current->Size() >= minimum_size) {
if (previous == NULL) {
free_lists_[kNumLists] = next;
} else {
previous->set_next(next);
}
return current;
}
previous = current;
current = next;
}
return NULL;
}
} // namespace dart
+4 -1
View File
@@ -92,12 +92,15 @@ class FreeList {
uword TryAllocateLocked(intptr_t size, bool is_protected);
void FreeLocked(uword addr, intptr_t size);
// Returns a large element, at least 'minimum_size', or NULL if none exists.
FreeListElement* TryAllocateLarge(intptr_t minimum_size);
private:
static const int kNumLists = 128;
static intptr_t IndexForSize(intptr_t size);
intptr_t Length(int index) const;
intptr_t LengthLocked(int index) const;
void EnqueueElement(FreeListElement* element, intptr_t index);
FreeListElement* DequeueElement(intptr_t index);
+42 -1
View File
@@ -33,11 +33,18 @@ DEFINE_FLAG(bool, verify_after_gc, false,
DEFINE_FLAG(bool, gc_at_alloc, false, "GC at every allocation.");
DEFINE_FLAG(int, new_gen_ext_limit, 64,
"maximum total external size (MB) in new gen before triggering GC");
DEFINE_FLAG(int, pretenure_threshold, 98,
"Trigger pretenuring when this many percent are promoted.");
DEFINE_FLAG(int, pretenure_interval, 10,
"Back off pretenuring after this many cycles.");
Heap::Heap(Isolate* isolate,
intptr_t max_new_gen_semi_words,
intptr_t max_old_gen_words)
: isolate_(isolate), read_only_(false), gc_in_progress_(false) {
: isolate_(isolate),
read_only_(false),
gc_in_progress_(false),
pretenure_policy_(0) {
for (int sel = 0;
sel < kNumWeakSelectors;
sel++) {
@@ -138,6 +145,15 @@ uword Heap::AllocateOld(intptr_t size, HeapPage::PageType type) {
return 0;
}
uword Heap::AllocatePretenured(intptr_t size) {
ASSERT(isolate()->no_gc_scope_depth() == 0);
uword addr = old_space_->TryAllocateDataBump(size, PageSpace::kControlGrowth);
if (addr != 0) return addr;
return AllocateOld(size, HeapPage::kData);
}
void Heap::AllocateExternal(intptr_t size, Space space) {
ASSERT(isolate()->no_gc_scope_depth() == 0);
if (space == kNew) {
@@ -269,6 +285,7 @@ void Heap::CollectGarbage(Space space,
UpdateClassHeapStatsBeforeGC(kNew);
new_space_->Scavenge(invoke_api_callbacks);
isolate()->class_table()->UpdatePromoted();
UpdatePretenurePolicy();
RecordAfterGC();
PrintStats();
if (old_space_->NeedsGarbageCollection()) {
@@ -321,6 +338,7 @@ void Heap::CollectAllGarbage() {
UpdateClassHeapStatsBeforeGC(kNew);
new_space_->Scavenge(kInvokeApiCallbacks);
isolate()->class_table()->UpdatePromoted();
UpdatePretenurePolicy();
RecordAfterGC();
PrintStats();
}
@@ -335,6 +353,29 @@ void Heap::CollectAllGarbage() {
}
bool Heap::ShouldPretenure(intptr_t class_id) const {
if (class_id == kOneByteStringCid) {
return pretenure_policy_ > 0;
} else {
return false;
}
}
void Heap::UpdatePretenurePolicy() {
ClassHeapStats* stats =
isolate_->class_table()->StatsWithUpdatedSize(kOneByteStringCid);
int allocated = stats->pre_gc.new_count;
int promo_percent = (allocated == 0) ? 0 :
(100 * stats->promoted_count) / allocated;
if (promo_percent >= FLAG_pretenure_threshold) {
pretenure_policy_ += FLAG_pretenure_interval;
} else {
pretenure_policy_ = Utils::Maximum(0, pretenure_policy_ - 1);
}
}
void Heap::SetGrowthControlState(bool state) {
old_space_->SetGrowthControlState(state);
}
+9
View File
@@ -33,6 +33,7 @@ class Heap {
kNew,
kOld,
kCode,
kPretenured,
};
enum WeakSelector {
@@ -78,6 +79,8 @@ class Heap {
return AllocateOld(size, HeapPage::kData);
case kCode:
return AllocateOld(size, HeapPage::kExecutable);
case kPretenured:
return AllocatePretenured(size);
default:
UNREACHABLE();
}
@@ -236,6 +239,8 @@ class Heap {
Isolate* isolate() const { return isolate_; }
bool ShouldPretenure(intptr_t class_id) const;
private:
class GCStats : public ValueObject {
public:
@@ -275,12 +280,14 @@ class Heap {
uword AllocateNew(intptr_t size);
uword AllocateOld(intptr_t size, HeapPage::PageType type);
uword AllocatePretenured(intptr_t size);
// GC stats collection.
void RecordBeforeGC(Space space, GCReason reason);
void RecordAfterGC();
void PrintStats();
void UpdateClassHeapStatsBeforeGC(Heap::Space space);
void UpdatePretenurePolicy();
// If this heap is non-empty, updates start and end to the smallest range that
// contains both the original [start, end) and the [lowest, highest) addresses
@@ -305,6 +312,8 @@ class Heap {
// GC on the heap is in progress.
bool gc_in_progress_;
int pretenure_policy_;
friend class GCEvent;
friend class GCTestHelper;
DISALLOW_COPY_AND_ASSIGN(Heap);
+94 -28
View File
@@ -19,7 +19,7 @@ DEFINE_FLAG(int, heap_growth_space_ratio, 20,
"The desired maximum percentage of free space after GC");
DEFINE_FLAG(int, heap_growth_time_ratio, 3,
"The desired maximum percentage of time spent in GC");
DEFINE_FLAG(int, heap_growth_rate, 256,
DEFINE_FLAG(int, heap_growth_rate, 280,
"The max number of pages the heap can grow at a time");
DEFINE_FLAG(bool, print_free_list_before_gc, false,
"Print free list statistics before a GC");
@@ -35,6 +35,7 @@ DEFINE_FLAG(bool, always_drop_code, false,
"Always try to drop code if the function's usage counter is >= 0");
DEFINE_FLAG(bool, concurrent_sweep, false,
"Concurrent sweep for old generation.");
DEFINE_FLAG(bool, log_growth, false, "Log PageSpace growth policy decisions.");
HeapPage* HeapPage::Initialize(VirtualMemory* memory, PageType type) {
ASSERT(memory->size() > VirtualMemory::PageSize());
@@ -129,6 +130,8 @@ PageSpace::PageSpace(Heap* heap, intptr_t max_capacity_in_words)
exec_pages_(NULL),
exec_pages_tail_(NULL),
large_pages_(NULL),
bump_top_(0),
bump_end_(0),
max_capacity_in_words_(max_capacity_in_words),
tasks_lock_(new Monitor()),
tasks_(0),
@@ -281,6 +284,39 @@ void PageSpace::FreePages(HeapPage* pages) {
}
uword PageSpace::TryAllocateInFreshPage(intptr_t size,
HeapPage::PageType type,
GrowthPolicy growth_policy,
bool is_locked) {
ASSERT(size < kAllocatablePageSize);
uword result = 0;
SpaceUsage after_allocation = usage_;
after_allocation.used_in_words += size >> kWordSizeLog2;
// Can we grow by one page?
after_allocation.capacity_in_words += kPageSizeInWords;
if ((growth_policy == kForceGrowth ||
!page_space_controller_.NeedsGarbageCollection(after_allocation)) &&
CanIncreaseCapacityInWords(kPageSizeInWords)) {
HeapPage* page = AllocatePage(type);
ASSERT(page != NULL);
// Start of the newly allocated page is the allocated object.
result = page->object_start();
usage_ = after_allocation;
// Enqueue the remainder in the free list.
uword free_start = result + size;
intptr_t free_size = page->object_end() - free_start;
if (free_size > 0) {
if (is_locked) {
freelist_[type].FreeLocked(free_start, free_size);
} else {
freelist_[type].Free(free_start, free_size);
}
}
}
return result;
}
uword PageSpace::TryAllocateInternal(intptr_t size,
HeapPage::PageType type,
GrowthPolicy growth_policy,
@@ -289,8 +325,6 @@ uword PageSpace::TryAllocateInternal(intptr_t size,
ASSERT(size >= kObjectAlignment);
ASSERT(Utils::IsAligned(size, kObjectAlignment));
uword result = 0;
SpaceUsage after_allocation = usage_;
after_allocation.used_in_words += size >> kWordSizeLog2;
if (size < kAllocatablePageSize) {
if (is_locked) {
result = freelist_[type].TryAllocateLocked(size, is_protected);
@@ -298,26 +332,7 @@ uword PageSpace::TryAllocateInternal(intptr_t size,
result = freelist_[type].TryAllocate(size, is_protected);
}
if (result == 0) {
// Can we grow by one page?
after_allocation.capacity_in_words += kPageSizeInWords;
if ((!page_space_controller_.NeedsGarbageCollection(after_allocation) ||
growth_policy == kForceGrowth) &&
CanIncreaseCapacityInWords(kPageSizeInWords)) {
HeapPage* page = AllocatePage(type);
ASSERT(page != NULL);
// Start of the newly allocated page is the allocated object.
result = page->object_start();
// Enqueue the remainder in the free list.
uword free_start = result + size;
intptr_t free_size = page->object_end() - free_start;
if (free_size > 0) {
if (is_locked) {
freelist_[type].FreeLocked(free_start, free_size);
} else {
freelist_[type].Free(free_start, free_size);
}
}
}
result = TryAllocateInFreshPage(size, type, growth_policy, is_locked);
}
} else {
// Large page allocation.
@@ -326,18 +341,20 @@ uword PageSpace::TryAllocateInternal(intptr_t size,
// On overflow we fail to allocate.
return 0;
}
SpaceUsage after_allocation = usage_;
after_allocation.used_in_words += size >> kWordSizeLog2;
after_allocation.capacity_in_words += page_size_in_words;
if ((!page_space_controller_.NeedsGarbageCollection(after_allocation) ||
growth_policy == kForceGrowth) &&
if ((growth_policy == kForceGrowth ||
!page_space_controller_.NeedsGarbageCollection(after_allocation)) &&
CanIncreaseCapacityInWords(page_size_in_words)) {
HeapPage* page = AllocateLargePage(size, type);
if (page != NULL) {
result = page->object_start();
usage_ = after_allocation;
}
}
}
if (result != 0) {
usage_ = after_allocation;
if (FLAG_compiler_stats && (type == HeapPage::kExecutable)) {
CompilerStats::code_allocated += size;
}
@@ -628,7 +645,9 @@ void PageSpace::MarkSweep(bool invoke_api_callbacks) {
int64_t mid1 = OS::GetCurrentTimeMicros();
// Reset the bump allocation page to unused.
// Abandon the remainder of the bump allocation block.
bump_top_ = 0;
bump_end_ = 0;
// Reset the freelists and setup sweeping.
freelist_[HeapPage::kData].Reset();
freelist_[HeapPage::kExecutable].Reset();
@@ -735,6 +754,44 @@ void PageSpace::MarkSweep(bool invoke_api_callbacks) {
}
uword PageSpace::TryAllocateDataBump(intptr_t size,
GrowthPolicy growth_policy) {
ASSERT(size >= kObjectAlignment);
ASSERT(Utils::IsAligned(size, kObjectAlignment));
intptr_t remaining = bump_end_ - bump_top_;
if (remaining < size) {
// Checking this first would be logical, but needlessly slow.
if (size >= kAllocatablePageSize) {
return TryAllocate(size, HeapPage::kData, growth_policy);
}
FreeListElement* block = freelist_[HeapPage::kData].TryAllocateLarge(size);
if (block == NULL) {
// Allocating from a new page (if growth policy allows) will have the
// side-effect of populating the freelist with a large block. The next
// bump allocation request will have a chance to consume that block.
// TODO(koda): Could take freelist lock just once instead of twice.
return TryAllocateInFreshPage(size,
HeapPage::kData,
growth_policy,
/* is_locked = */ false);
}
intptr_t block_size = block->Size();
bump_top_ = reinterpret_cast<uword>(block);
bump_end_ = bump_top_ + block_size;
remaining = block_size;
}
ASSERT(remaining >= size);
uword result = bump_top_;
bump_top_ += size;
usage_.used_in_words += size >> kWordSizeLog2;
remaining -= size;
if (remaining > 0) {
FreeListElement::AsElement(bump_top_, remaining);
}
return result;
}
PageSpaceController::PageSpaceController(Heap* heap,
int heap_growth_ratio,
int heap_growth_max,
@@ -780,7 +837,16 @@ bool PageSpaceController::NeedsGarbageCollection(SpaceUsage after) const {
multiplier *= seconds_since_init / kInitialTimeoutSeconds;
}
}
return capacity_increase_in_pages * multiplier > grow_heap_;
bool needs_gc = capacity_increase_in_pages * multiplier > grow_heap_;
if (FLAG_log_growth) {
OS::PrintErr("%s: %" Pd " * %f %s %" Pd "\n",
needs_gc ? "NEEDS GC" : "grow",
capacity_increase_in_pages,
multiplier,
needs_gc ? ">" : "<=",
grow_heap_);
}
return needs_gc;
}
+12
View File
@@ -303,6 +303,9 @@ class PageSpace {
tasks_ = val;
}
// Attempt to allocate from bump block rather than normal freelist.
uword TryAllocateDataBump(intptr_t size, GrowthPolicy growth_policy);
private:
// Ids for time and data records in Heap::GCStats.
enum {
@@ -325,6 +328,10 @@ class PageSpace {
GrowthPolicy growth_policy,
bool is_protected,
bool is_locked);
uword TryAllocateInFreshPage(intptr_t size,
HeapPage::PageType type,
GrowthPolicy growth_policy,
bool is_locked);
HeapPage* AllocatePage(HeapPage::PageType type);
void FreePage(HeapPage* page, HeapPage* previous_page);
HeapPage* AllocateLargePage(intptr_t size, HeapPage::PageType type);
@@ -358,6 +365,11 @@ class PageSpace {
HeapPage* exec_pages_tail_;
HeapPage* large_pages_;
// A block of memory in a data page, managed by bump allocation. The remainder
// is kept formatted as a FreeListElement, but is not in any freelist.
uword bump_top_;
uword bump_end_;
// Various sizes being tracked for this generation.
intptr_t max_capacity_in_words_;
SpaceUsage usage_;