diff options
| author | 2013-07-25 15:19:42 -0700 | |
|---|---|---|
| committer | 2013-08-06 17:55:52 -0700 | |
| commit | eb5710eba75bf338da56386ca29039df9d5134cb (patch) | |
| tree | aedf2f265628097f10754e04b5b3f6c83ee87d46 | |
| parent | 9642c96bd5a1ccc4e221de9c0af4a545af8182d2 (diff) | |
New free list large object space.
More memory efficient, uses a std::set instead of multiset. Each
large object allocation has sizeof(size_t) * 2 bytes of overhead
without taking into account the std::set overhead. If debug is
enabled, then objects are mprotected as they are freed.
Added a large object test to space test.
Change-Id: I3e714e1afbed49208a4a7e60f9ef7d701a56801b
| -rw-r--r-- | runtime/gc/heap.cc | 34 | ||||
| -rw-r--r-- | runtime/gc/space/dlmalloc_space-inl.h | 5 | ||||
| -rw-r--r-- | runtime/gc/space/dlmalloc_space.h | 2 | ||||
| -rw-r--r-- | runtime/gc/space/large_object_space.cc | 249 | ||||
| -rw-r--r-- | runtime/gc/space/large_object_space.h | 104 | ||||
| -rw-r--r-- | runtime/gc/space/space_test.cc | 124 |
6 files changed, 335 insertions, 183 deletions
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 74f7b1b755..668ff1db6b 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -150,8 +150,16 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max } } + alloc_space_ = space::DlMallocSpace::Create(Runtime::Current()->IsZygote() ? "zygote space" : "alloc space", + initial_size, + growth_limit, capacity, + requested_alloc_space_begin); + CHECK(alloc_space_ != NULL) << "Failed to create alloc space"; + alloc_space_->SetFootprintLimit(alloc_space_->Capacity()); + AddContinuousSpace(alloc_space_); + // Allocate the large object space. - const bool kUseFreeListSpaceForLOS = false; + const bool kUseFreeListSpaceForLOS = false; if (kUseFreeListSpaceForLOS) { large_object_space_ = space::FreeListSpace::Create("large object space", NULL, capacity); } else { @@ -160,14 +168,6 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max CHECK(large_object_space_ != NULL) << "Failed to create large object space"; AddDiscontinuousSpace(large_object_space_); - alloc_space_ = space::DlMallocSpace::Create(Runtime::Current()->IsZygote() ? "zygote space" : "alloc space", - initial_size, - growth_limit, capacity, - requested_alloc_space_begin); - CHECK(alloc_space_ != NULL) << "Failed to create alloc space"; - alloc_space_->SetFootprintLimit(alloc_space_->Capacity()); - AddContinuousSpace(alloc_space_); - // Compute heap capacity. Continuous spaces are sorted in order of Begin(). byte* heap_begin = continuous_spaces_.front()->Begin(); size_t heap_capacity = continuous_spaces_.back()->End() - continuous_spaces_.front()->Begin(); @@ -448,8 +448,7 @@ mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_co DCHECK_GE(byte_count, sizeof(mirror::Object)); mirror::Object* obj = NULL; - size_t size = 0; - size_t bytes_allocated; + size_t bytes_allocated = 0; uint64_t allocation_start = 0; if (UNLIKELY(kMeasureAllocationTime)) { allocation_start = NanoTime() / kTimeAdjust; @@ -457,14 +456,12 @@ mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_co // We need to have a zygote space or else our newly allocated large object can end up in the // Zygote resulting in it being prematurely freed. - // We can only do this for primive objects since large objects will not be within the card table + // We can only do this for primitive objects since large objects will not be within the card table // range. This also means that we rely on SetClass not dirtying the object's card. bool large_object_allocation = byte_count >= large_object_threshold_ && have_zygote_space_ && c->IsPrimitiveArray(); if (UNLIKELY(large_object_allocation)) { - size = RoundUp(byte_count, kPageSize); - obj = Allocate(self, large_object_space_, size, &bytes_allocated); - DCHECK(obj == NULL || size == bytes_allocated); + obj = Allocate(self, large_object_space_, byte_count, &bytes_allocated); // Make sure that our large object didn't get placed anywhere within the space interval or else // it breaks the immune range. DCHECK(obj == NULL || @@ -472,9 +469,6 @@ mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_co reinterpret_cast<byte*>(obj) >= continuous_spaces_.back()->End()); } else { obj = Allocate(self, alloc_space_, byte_count, &bytes_allocated); - DCHECK(obj == NULL || size <= bytes_allocated); - size = bytes_allocated; - // Ensure that we did not allocate into a zygote space. DCHECK(obj == NULL || !have_zygote_space_ || !FindSpaceFromObject(obj, false)->IsZygoteSpace()); } @@ -484,12 +478,12 @@ mirror::Object* Heap::AllocObject(Thread* self, mirror::Class* c, size_t byte_co // Record allocation after since we want to use the atomic add for the atomic fence to guard // the SetClass since we do not want the class to appear NULL in another thread. - RecordAllocation(size, obj); + RecordAllocation(bytes_allocated, obj); if (Dbg::IsAllocTrackingEnabled()) { Dbg::RecordAllocation(c, byte_count); } - if (static_cast<size_t>(num_bytes_allocated_) >= concurrent_start_bytes_) { + if (UNLIKELY(static_cast<size_t>(num_bytes_allocated_) >= concurrent_start_bytes_)) { // The SirtRef is necessary since the calls in RequestConcurrentGC are a safepoint. SirtRef<mirror::Object> ref(self, obj); RequestConcurrentGC(self); diff --git a/runtime/gc/space/dlmalloc_space-inl.h b/runtime/gc/space/dlmalloc_space-inl.h index 849157fb5d..54811414e9 100644 --- a/runtime/gc/space/dlmalloc_space-inl.h +++ b/runtime/gc/space/dlmalloc_space-inl.h @@ -45,9 +45,8 @@ inline mirror::Object* DlMallocSpace::AllocWithoutGrowthLocked(size_t num_bytes, << ") not in bounds of allocation space " << *this; } size_t allocation_size = AllocationSizeNonvirtual(result); - if (bytes_allocated != NULL) { - *bytes_allocated = allocation_size; - } + DCHECK(bytes_allocated != NULL); + *bytes_allocated = allocation_size; num_bytes_allocated_ += allocation_size; total_bytes_allocated_ += allocation_size; ++total_objects_allocated_; diff --git a/runtime/gc/space/dlmalloc_space.h b/runtime/gc/space/dlmalloc_space.h index c498ef8ce6..6d52c269e6 100644 --- a/runtime/gc/space/dlmalloc_space.h +++ b/runtime/gc/space/dlmalloc_space.h @@ -79,7 +79,7 @@ class DlMallocSpace : public MemMapSpace, public AllocSpace { // Perform a mspace_inspect_all which calls back for each allocation chunk. The chunk may not be // in use, indicated by num_bytes equaling zero. - void Walk(WalkCallback callback, void* arg); + void Walk(WalkCallback callback, void* arg) LOCKS_EXCLUDED(lock_); // Returns the number of bytes that the space has currently obtained from the system. This is // greater or equal to the amount of live data in the space. diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc index 832c6fa297..a174c0a1dc 100644 --- a/runtime/gc/space/large_object_space.cc +++ b/runtime/gc/space/large_object_space.cc @@ -66,9 +66,8 @@ mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, size_ large_objects_.push_back(obj); mem_maps_.Put(obj, mem_map); size_t allocation_size = mem_map->Size(); - if (bytes_allocated != NULL) { - *bytes_allocated = allocation_size; - } + DCHECK(bytes_allocated != NULL); + *bytes_allocated = allocation_size; num_bytes_allocated_ += allocation_size; total_bytes_allocated_ += allocation_size; ++num_objects_allocated_; @@ -141,89 +140,97 @@ FreeListSpace::FreeListSpace(const std::string& name, MemMap* mem_map, byte* beg end_(end), mem_map_(mem_map), lock_("free list space lock", kAllocSpaceLock) { - chunks_.resize(Size() / kAlignment + 1); - // Add a dummy chunk so we don't need to handle chunks having no next chunk. - chunks_.back().SetSize(kAlignment, false); - // Start out with one large free chunk. - AddFreeChunk(begin_, end_ - begin_, NULL); + free_end_ = end - begin; } FreeListSpace::~FreeListSpace() {} -void FreeListSpace::AddFreeChunk(void* address, size_t size, Chunk* previous) { - Chunk* chunk = ChunkFromAddr(address); - chunk->SetSize(size, true); - chunk->SetPrevious(previous); - Chunk* next_chunk = GetNextChunk(chunk); - next_chunk->SetPrevious(chunk); - free_chunks_.insert(chunk); +void FreeListSpace::Walk(DlMallocSpace::WalkCallback callback, void* arg) { + MutexLock mu(Thread::Current(), lock_); + uintptr_t free_end_start = reinterpret_cast<uintptr_t>(end_) - free_end_; + AllocationHeader* cur_header = reinterpret_cast<AllocationHeader*>(Begin()); + while (reinterpret_cast<uintptr_t>(cur_header) < free_end_start) { + cur_header = cur_header->GetNextNonFree(); + size_t alloc_size = cur_header->AllocationSize(); + byte* byte_start = reinterpret_cast<byte*>(cur_header->GetObjectAddress()); + byte* byte_end = byte_start + alloc_size - sizeof(AllocationHeader); + callback(byte_start, byte_end, alloc_size, arg); + callback(NULL, NULL, 0, arg); + cur_header = reinterpret_cast<AllocationHeader*>(byte_end); + } } -FreeListSpace::Chunk* FreeListSpace::ChunkFromAddr(void* address) { - size_t offset = reinterpret_cast<byte*>(address) - Begin(); - DCHECK(IsAligned<kAlignment>(offset)); - DCHECK_LT(offset, Size()); - return &chunks_[offset / kAlignment]; +void FreeListSpace::RemoveFreePrev(AllocationHeader* header) { + CHECK(!header->IsFree()); + CHECK_GT(header->GetPrevFree(), size_t(0)); + FreeBlocks::iterator found = free_blocks_.lower_bound(header); + CHECK(found != free_blocks_.end()); + CHECK_EQ(*found, header); + free_blocks_.erase(found); } -void* FreeListSpace::AddrFromChunk(Chunk* chunk) { - return reinterpret_cast<void*>(Begin() + (chunk - &chunks_.front()) * kAlignment); +FreeListSpace::AllocationHeader* FreeListSpace::GetAllocationHeader(const mirror::Object* obj) { + DCHECK(Contains(obj)); + return reinterpret_cast<AllocationHeader*>(reinterpret_cast<uintptr_t>(obj) - + sizeof(AllocationHeader)); } -void FreeListSpace::RemoveFreeChunk(Chunk* chunk) { - // TODO: C++0x - // TODO: Improve performance, this might be slow. - std::pair<FreeChunks::iterator, FreeChunks::iterator> range = free_chunks_.equal_range(chunk); - for (FreeChunks::iterator it = range.first; it != range.second; ++it) { - if (*it == chunk) { - free_chunks_.erase(it); - return; - } - } -} - -void FreeListSpace::Walk(DlMallocSpace::WalkCallback callback, void* arg) { - MutexLock mu(Thread::Current(), lock_); - for (Chunk* chunk = &chunks_.front(); chunk < &chunks_.back(); ) { - if (!chunk->IsFree()) { - size_t size = chunk->GetSize(); - void* begin = AddrFromChunk(chunk); - void* end = reinterpret_cast<void*>(reinterpret_cast<byte*>(begin) + size); - callback(begin, end, size, arg); - callback(NULL, NULL, 0, arg); - } - chunk = GetNextChunk(chunk); +FreeListSpace::AllocationHeader* FreeListSpace::AllocationHeader::GetNextNonFree() { + // We know that there has to be at least one object after us or else we would have + // coalesced with the free end region. May be worth investigating a better way to do this + // as it may be expensive for large allocations. + for (uintptr_t pos = reinterpret_cast<uintptr_t>(this);; pos += kAlignment) { + AllocationHeader* cur = reinterpret_cast<AllocationHeader*>(pos); + if (!cur->IsFree()) return cur; } } size_t FreeListSpace::Free(Thread* self, mirror::Object* obj) { MutexLock mu(self, lock_); - CHECK(Contains(obj)); - // Check adjacent chunks to see if we need to combine. - Chunk* chunk = ChunkFromAddr(obj); - CHECK(!chunk->IsFree()); - - size_t allocation_size = chunk->GetSize(); - if (kIsDebugBuild) { - memset(obj, 0xEB, allocation_size); + DCHECK(Contains(obj)); + AllocationHeader* header = GetAllocationHeader(obj); + CHECK(IsAligned<kAlignment>(header)); + size_t allocation_size = header->AllocationSize(); + DCHECK_GT(allocation_size, size_t(0)); + DCHECK(IsAligned<kAlignment>(allocation_size)); + // Look at the next chunk. + AllocationHeader* next_header = header->GetNextAllocationHeader(); + // Calculate the start of the end free block. + uintptr_t free_end_start = reinterpret_cast<uintptr_t>(end_) - free_end_; + size_t header_prev_free = header->GetPrevFree(); + size_t new_free_size = allocation_size; + if (header_prev_free) { + new_free_size += header_prev_free; + RemoveFreePrev(header); } - madvise(obj, allocation_size, MADV_DONTNEED); - num_objects_allocated_--; - num_bytes_allocated_ -= allocation_size; - Chunk* prev = chunk->GetPrevious(); - Chunk* next = GetNextChunk(chunk); - - // Combine any adjacent free chunks - size_t extra_size = chunk->GetSize(); - if (next->IsFree()) { - extra_size += next->GetSize(); - RemoveFreeChunk(next); - } - if (prev != NULL && prev->IsFree()) { - RemoveFreeChunk(prev); - AddFreeChunk(AddrFromChunk(prev), prev->GetSize() + extra_size, prev->GetPrevious()); + if (reinterpret_cast<uintptr_t>(next_header) >= free_end_start) { + // Easy case, the next chunk is the end free region. + CHECK_EQ(reinterpret_cast<uintptr_t>(next_header), free_end_start); + free_end_ += new_free_size; } else { - AddFreeChunk(AddrFromChunk(chunk), extra_size, prev); + AllocationHeader* new_free_header; + DCHECK(IsAligned<kAlignment>(next_header)); + if (next_header->IsFree()) { + // Find the next chunk by reading each page until we hit one with non-zero chunk. + AllocationHeader* next_next_header = next_header->GetNextNonFree(); + DCHECK(IsAligned<kAlignment>(next_next_header)); + DCHECK(IsAligned<kAlignment>(next_next_header->AllocationSize())); + RemoveFreePrev(next_next_header); + new_free_header = next_next_header; + new_free_size += next_next_header->GetPrevFree(); + } else { + new_free_header = next_header; + } + new_free_header->prev_free_ = new_free_size; + free_blocks_.insert(new_free_header); + } + --num_objects_allocated_; + DCHECK_LE(allocation_size, num_bytes_allocated_); + num_bytes_allocated_ -= allocation_size; + madvise(header, allocation_size, MADV_DONTNEED); + if (kIsDebugBuild) { + // Can't disallow reads since we use them to find next chunks during coalescing. + mprotect(header, allocation_size, PROT_READ); } return allocation_size; } @@ -232,53 +239,91 @@ bool FreeListSpace::Contains(const mirror::Object* obj) const { return mem_map_->HasAddress(obj); } -FreeListSpace::Chunk* FreeListSpace::GetNextChunk(Chunk* chunk) { - return chunk + chunk->GetSize() / kAlignment; -} - size_t FreeListSpace::AllocationSize(const mirror::Object* obj) { - Chunk* chunk = ChunkFromAddr(const_cast<mirror::Object*>(obj)); - CHECK(!chunk->IsFree()); - return chunk->GetSize(); + AllocationHeader* header = GetAllocationHeader(obj); + DCHECK(Contains(obj)); + DCHECK(!header->IsFree()); + return header->AllocationSize(); } mirror::Object* FreeListSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated) { MutexLock mu(self, lock_); - num_bytes = RoundUp(num_bytes, kAlignment); - Chunk temp; - temp.SetSize(num_bytes); + size_t allocation_size = RoundUp(num_bytes + sizeof(AllocationHeader), kAlignment); + AllocationHeader temp; + temp.SetPrevFree(allocation_size); + temp.SetAllocationSize(0); + AllocationHeader* new_header; // Find the smallest chunk at least num_bytes in size. - FreeChunks::iterator found = free_chunks_.lower_bound(&temp); - if (found == free_chunks_.end()) { - // Out of memory, or too much fragmentation. - return NULL; - } - Chunk* chunk = *found; - free_chunks_.erase(found); - CHECK(chunk->IsFree()); - void* addr = AddrFromChunk(chunk); - size_t chunk_size = chunk->GetSize(); - chunk->SetSize(num_bytes); - if (chunk_size > num_bytes) { - // Split the chunk into two chunks. - Chunk* new_chunk = GetNextChunk(chunk); - AddFreeChunk(AddrFromChunk(new_chunk), chunk_size - num_bytes, chunk); + FreeBlocks::iterator found = free_blocks_.lower_bound(&temp); + if (found != free_blocks_.end()) { + AllocationHeader* header = *found; + free_blocks_.erase(found); + + // Fit our object in the previous free header space. + new_header = header->GetPrevFreeAllocationHeader(); + + // Remove the newly allocated block from the header and update the prev_free_. + header->prev_free_ -= allocation_size; + if (header->prev_free_ > 0) { + // If there is remaining space, insert back into the free set. + free_blocks_.insert(header); + } + } else { + // Try to steal some memory from the free space at the end of the space. + if (LIKELY(free_end_ >= allocation_size)) { + // Fit our object at the start of the end free block. + new_header = reinterpret_cast<AllocationHeader*>(end_ - free_end_); + free_end_ -= allocation_size; + } else { + return NULL; + } } - if (bytes_allocated != NULL) { - *bytes_allocated = num_bytes; + DCHECK(bytes_allocated != NULL); + *bytes_allocated = allocation_size; + + // Need to do these inside of the lock. + ++num_objects_allocated_; + ++total_objects_allocated_; + num_bytes_allocated_ += allocation_size; + total_bytes_allocated_ += allocation_size; + + // We always put our object at the start of the free block, there can not be another free block + // before it. + if (kIsDebugBuild) { + mprotect(new_header, allocation_size, PROT_READ | PROT_WRITE); } - num_objects_allocated_++; - total_objects_allocated_++; - num_bytes_allocated_ += num_bytes; - total_bytes_allocated_ += num_bytes; - return reinterpret_cast<mirror::Object*>(addr); + new_header->SetPrevFree(0); + new_header->SetAllocationSize(allocation_size); + return new_header->GetObjectAddress(); } void FreeListSpace::Dump(std::ostream& os) const { + MutexLock mu(Thread::Current(), const_cast<Mutex&>(lock_)); os << GetName() << " -" << " begin: " << reinterpret_cast<void*>(Begin()) - << " end: " << reinterpret_cast<void*>(End()); + << " end: " << reinterpret_cast<void*>(End()) << "\n"; + uintptr_t free_end_start = reinterpret_cast<uintptr_t>(end_) - free_end_; + AllocationHeader* cur_header = reinterpret_cast<AllocationHeader*>(Begin()); + while (reinterpret_cast<uintptr_t>(cur_header) < free_end_start) { + byte* free_start = reinterpret_cast<byte*>(cur_header); + cur_header = cur_header->GetNextNonFree(); + byte* free_end = reinterpret_cast<byte*>(cur_header); + if (free_start != free_end) { + os << "Free block at address: " << reinterpret_cast<const void*>(free_start) + << " of length " << free_end - free_start << " bytes\n"; + } + size_t alloc_size = cur_header->AllocationSize(); + byte* byte_start = reinterpret_cast<byte*>(cur_header->GetObjectAddress()); + byte* byte_end = byte_start + alloc_size - sizeof(AllocationHeader); + os << "Large object at address: " << reinterpret_cast<const void*>(free_start) + << " of length " << byte_end - byte_start << " bytes\n"; + cur_header = reinterpret_cast<AllocationHeader*>(byte_end); + } + if (free_end_) { + os << "Free block at address: " << reinterpret_cast<const void*>(free_end_start) + << " of length " << free_end_ << " bytes\n"; + } } } // namespace space diff --git a/runtime/gc/space/large_object_space.h b/runtime/gc/space/large_object_space.h index 31659db1cd..a703e86ea1 100644 --- a/runtime/gc/space/large_object_space.h +++ b/runtime/gc/space/large_object_space.h @@ -85,7 +85,7 @@ class LargeObjectMapSpace : public LargeObjectSpace { size_t AllocationSize(const mirror::Object* obj); mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated); size_t Free(Thread* self, mirror::Object* ptr); - void Walk(DlMallocSpace::WalkCallback, void* arg); + void Walk(DlMallocSpace::WalkCallback, void* arg) LOCKS_EXCLUDED(lock_); // TODO: disabling thread safety analysis as this may be called when we already hold lock_. bool Contains(const mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS; @@ -103,7 +103,6 @@ class LargeObjectMapSpace : public LargeObjectSpace { }; // A continuous large object space with a free-list to handle holes. -// TODO: this implementation is buggy. class FreeListSpace : public LargeObjectSpace { public: virtual ~FreeListSpace(); @@ -113,7 +112,7 @@ class FreeListSpace : public LargeObjectSpace { mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated); size_t Free(Thread* self, mirror::Object* obj); bool Contains(const mirror::Object* obj) const; - void Walk(DlMallocSpace::WalkCallback callback, void* arg); + void Walk(DlMallocSpace::WalkCallback callback, void* arg) LOCKS_EXCLUDED(lock_); // Address at which the space begins. byte* Begin() const { @@ -135,57 +134,100 @@ class FreeListSpace : public LargeObjectSpace { private: static const size_t kAlignment = kPageSize; - class Chunk { + class AllocationHeader { public: - static const size_t kFreeFlag = 0x80000000; + // Returns the allocation size, includes the header. + size_t AllocationSize() const { + return alloc_size_; + } - struct SortBySize { - bool operator()(const Chunk* a, const Chunk* b) const { - return a->GetSize() < b->GetSize(); - } - }; + // Updates the allocation size in the header, the allocation size includes the header itself. + void SetAllocationSize(size_t size) { + DCHECK(IsAligned<kPageSize>(size)); + alloc_size_ = size; + } bool IsFree() const { - return (m_size & kFreeFlag) != 0; + return AllocationSize() == 0; } - void SetSize(size_t size, bool is_free = false) { - m_size = size | (is_free ? kFreeFlag : 0); + // Returns the previous free allocation header by using the prev_free_ member to figure out + // where it is. If prev free is 0 then we just return ourself. + AllocationHeader* GetPrevFreeAllocationHeader() { + return reinterpret_cast<AllocationHeader*>(reinterpret_cast<uintptr_t>(this) - prev_free_); } - size_t GetSize() const { - return m_size & (kFreeFlag - 1); + // Returns the address of the object associated with this allocation header. + mirror::Object* GetObjectAddress() { + return reinterpret_cast<mirror::Object*>(reinterpret_cast<uintptr_t>(this) + sizeof(*this)); } - Chunk* GetPrevious() { - return m_previous; + // Returns the next allocation header after the object associated with this allocation header. + AllocationHeader* GetNextAllocationHeader() { + DCHECK_NE(alloc_size_, 0U); + return reinterpret_cast<AllocationHeader*>(reinterpret_cast<uintptr_t>(this) + alloc_size_); } - void SetPrevious(Chunk* previous) { - m_previous = previous; - DCHECK(m_previous == NULL || - (m_previous != NULL && m_previous + m_previous->GetSize() / kAlignment == this)); + // Returns how many free bytes there is before the block. + size_t GetPrevFree() const { + return prev_free_; } + // Update the size of the free block prior to the allocation. + void SetPrevFree(size_t prev_free) { + DCHECK(IsAligned<kPageSize>(prev_free)); + prev_free_ = prev_free; + } + + // Finds and returns the next non free allocation header after ourself. + // TODO: Optimize, currently O(n) for n free following pages. + AllocationHeader* GetNextNonFree(); + + // Used to implement best fit object allocation. Each allocation has an AllocationHeader which + // contains the size of the previous free block preceding it. Implemented in such a way that we + // can also find the iterator for any allocation header pointer. + class SortByPrevFree { + public: + bool operator()(const AllocationHeader* a, const AllocationHeader* b) const { + if (a->GetPrevFree() < b->GetPrevFree()) return true; + if (a->GetPrevFree() > b->GetPrevFree()) return false; + if (a->AllocationSize() < b->AllocationSize()) return true; + if (a->AllocationSize() > b->AllocationSize()) return false; + return reinterpret_cast<uintptr_t>(a) < reinterpret_cast<uintptr_t>(b); + } + }; + private: - size_t m_size; - Chunk* m_previous; + // Contains the size of the previous free block, if 0 then the memory preceding us is an + // allocation. + size_t prev_free_; + + // Allocation size of this object, 0 means that the allocation header is free memory. + size_t alloc_size_; + + friend class FreeListSpace; }; FreeListSpace(const std::string& name, MemMap* mem_map, byte* begin, byte* end); - void AddFreeChunk(void* address, size_t size, Chunk* previous) EXCLUSIVE_LOCKS_REQUIRED(lock_); - Chunk* ChunkFromAddr(void* address) EXCLUSIVE_LOCKS_REQUIRED(lock_); - void* AddrFromChunk(Chunk* chunk) EXCLUSIVE_LOCKS_REQUIRED(lock_); - void RemoveFreeChunk(Chunk* chunk) EXCLUSIVE_LOCKS_REQUIRED(lock_); - Chunk* GetNextChunk(Chunk* chunk); - typedef std::multiset<Chunk*, Chunk::SortBySize> FreeChunks; + // Removes header from the free blocks set by finding the corresponding iterator and erasing it. + void RemoveFreePrev(AllocationHeader* header) EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Finds the allocation header corresponding to obj. + AllocationHeader* GetAllocationHeader(const mirror::Object* obj); + + typedef std::set<AllocationHeader*, AllocationHeader::SortByPrevFree, + accounting::GCAllocator<AllocationHeader*> > FreeBlocks; + byte* const begin_; byte* const end_; + + // There is not footer for any allocations at the end of the space, so we keep track of how much + // free space there is at the end manually. UniquePtr<MemMap> mem_map_; Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; - std::vector<Chunk> chunks_ GUARDED_BY(lock_); - FreeChunks free_chunks_ GUARDED_BY(lock_); + size_t free_end_ GUARDED_BY(lock_); + FreeBlocks free_blocks_ GUARDED_BY(lock_); }; } // namespace space diff --git a/runtime/gc/space/space_test.cc b/runtime/gc/space/space_test.cc index 66b8c11b59..455168c90f 100644 --- a/runtime/gc/space/space_test.cc +++ b/runtime/gc/space/space_test.cc @@ -15,6 +15,7 @@ */ #include "dlmalloc_space.h" +#include "large_object_space.h" #include "common_test.h" #include "globals.h" @@ -37,6 +38,11 @@ class SpaceTest : public CommonTest { } }; +static size_t test_rand(size_t* seed) { + *seed = *seed * 1103515245 + 12345; + return *seed; +} + TEST_F(SpaceTest, Init) { { // Init < max == growth @@ -80,6 +86,7 @@ TEST_F(SpaceTest, Init) { // allocations after the ZygoteSpace is created. The test should also do some GCs to ensure that // the GC works with the ZygoteSpace. TEST_F(SpaceTest, ZygoteSpace) { + size_t dummy = 0; DlMallocSpace* space(DlMallocSpace::Create("test", 4 * MB, 16 * MB, 16 * MB, NULL)); ASSERT_TRUE(space != NULL); @@ -88,11 +95,11 @@ TEST_F(SpaceTest, ZygoteSpace) { Thread* self = Thread::Current(); // Succeeds, fits without adjusting the footprint limit. - mirror::Object* ptr1 = space->Alloc(self, 1 * MB, NULL); + mirror::Object* ptr1 = space->Alloc(self, 1 * MB, &dummy); EXPECT_TRUE(ptr1 != NULL); // Fails, requires a higher footprint limit. - mirror::Object* ptr2 = space->Alloc(self, 8 * MB, NULL); + mirror::Object* ptr2 = space->Alloc(self, 8 * MB, &dummy); EXPECT_TRUE(ptr2 == NULL); // Succeeds, adjusts the footprint. @@ -102,11 +109,11 @@ TEST_F(SpaceTest, ZygoteSpace) { EXPECT_LE(8U * MB, ptr3_bytes_allocated); // Fails, requires a higher footprint limit. - mirror::Object* ptr4 = space->Alloc(self, 8 * MB, NULL); + mirror::Object* ptr4 = space->Alloc(self, 8 * MB, &dummy); EXPECT_TRUE(ptr4 == NULL); // Also fails, requires a higher allowed footprint. - mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, NULL); + mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, &dummy); EXPECT_TRUE(ptr5 == NULL); // Release some memory. @@ -116,7 +123,7 @@ TEST_F(SpaceTest, ZygoteSpace) { EXPECT_LE(8U * MB, free3); // Succeeds, now that memory has been freed. - void* ptr6 = space->AllocWithGrowth(self, 9 * MB, NULL); + void* ptr6 = space->AllocWithGrowth(self, 9 * MB, &dummy); EXPECT_TRUE(ptr6 != NULL); // Final clean up. @@ -125,22 +132,22 @@ TEST_F(SpaceTest, ZygoteSpace) { EXPECT_LE(1U * MB, free1); // Make sure that the zygote space isn't directly at the start of the space. - space->Alloc(self, 1U * MB, NULL); + space->Alloc(self, 1U * MB, &dummy); space = space->CreateZygoteSpace("alloc space"); // Make space findable to the heap, will also delete space when runtime is cleaned up AddContinuousSpace(space); // Succeeds, fits without adjusting the footprint limit. - ptr1 = space->Alloc(self, 1 * MB, NULL); + ptr1 = space->Alloc(self, 1 * MB, &dummy); EXPECT_TRUE(ptr1 != NULL); // Fails, requires a higher footprint limit. - ptr2 = space->Alloc(self, 8 * MB, NULL); + ptr2 = space->Alloc(self, 8 * MB, &dummy); EXPECT_TRUE(ptr2 == NULL); // Succeeds, adjusts the footprint. - ptr3 = space->AllocWithGrowth(self, 2 * MB, NULL); + ptr3 = space->AllocWithGrowth(self, 2 * MB, &dummy); EXPECT_TRUE(ptr3 != NULL); space->Free(self, ptr3); @@ -151,6 +158,7 @@ TEST_F(SpaceTest, ZygoteSpace) { } TEST_F(SpaceTest, AllocAndFree) { + size_t dummy = 0; DlMallocSpace* space(DlMallocSpace::Create("test", 4 * MB, 16 * MB, 16 * MB, NULL)); ASSERT_TRUE(space != NULL); Thread* self = Thread::Current(); @@ -159,11 +167,11 @@ TEST_F(SpaceTest, AllocAndFree) { AddContinuousSpace(space); // Succeeds, fits without adjusting the footprint limit. - mirror::Object* ptr1 = space->Alloc(self, 1 * MB, NULL); + mirror::Object* ptr1 = space->Alloc(self, 1 * MB, &dummy); EXPECT_TRUE(ptr1 != NULL); // Fails, requires a higher footprint limit. - mirror::Object* ptr2 = space->Alloc(self, 8 * MB, NULL); + mirror::Object* ptr2 = space->Alloc(self, 8 * MB, &dummy); EXPECT_TRUE(ptr2 == NULL); // Succeeds, adjusts the footprint. @@ -173,11 +181,11 @@ TEST_F(SpaceTest, AllocAndFree) { EXPECT_LE(8U * MB, ptr3_bytes_allocated); // Fails, requires a higher footprint limit. - mirror::Object* ptr4 = space->Alloc(self, 8 * MB, NULL); + mirror::Object* ptr4 = space->Alloc(self, 8 * MB, &dummy); EXPECT_TRUE(ptr4 == NULL); // Also fails, requires a higher allowed footprint. - mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, NULL); + mirror::Object* ptr5 = space->AllocWithGrowth(self, 8 * MB, &dummy); EXPECT_TRUE(ptr5 == NULL); // Release some memory. @@ -187,7 +195,7 @@ TEST_F(SpaceTest, AllocAndFree) { EXPECT_LE(8U * MB, free3); // Succeeds, now that memory has been freed. - void* ptr6 = space->AllocWithGrowth(self, 9 * MB, NULL); + void* ptr6 = space->AllocWithGrowth(self, 9 * MB, &dummy); EXPECT_TRUE(ptr6 != NULL); // Final clean up. @@ -196,6 +204,67 @@ TEST_F(SpaceTest, AllocAndFree) { EXPECT_LE(1U * MB, free1); } +TEST_F(SpaceTest, LargeObjectTest) { + size_t rand_seed = 0; + for (size_t i = 0; i < 2; ++i) { + LargeObjectSpace* los = NULL; + if (i == 0) { + los = space::LargeObjectMapSpace::Create("large object space"); + } else { + los = space::FreeListSpace::Create("large object space", NULL, 128 * MB); + } + + static const size_t num_allocations = 64; + static const size_t max_allocation_size = 0x100000; + std::vector<std::pair<mirror::Object*, size_t> > requests; + + for (size_t phase = 0; phase < 2; ++phase) { + while (requests.size() < num_allocations) { + size_t request_size = test_rand(&rand_seed) % max_allocation_size; + size_t allocation_size = 0; + mirror::Object* obj = los->Alloc(Thread::Current(), request_size, &allocation_size); + ASSERT_TRUE(obj != NULL); + ASSERT_EQ(allocation_size, los->AllocationSize(obj)); + ASSERT_GE(allocation_size, request_size); + // Fill in our magic value. + byte magic = (request_size & 0xFF) | 1; + memset(obj, magic, request_size); + requests.push_back(std::make_pair(obj, request_size)); + } + + // "Randomly" shuffle the requests. + for (size_t k = 0; k < 10; ++k) { + for (size_t j = 0; j < requests.size(); ++j) { + std::swap(requests[j], requests[test_rand(&rand_seed) % requests.size()]); + } + } + + // Free 1 / 2 the allocations the first phase, and all the second phase. + size_t limit = !phase ? requests.size() / 2 : 0; + while (requests.size() > limit) { + mirror::Object* obj = requests.back().first; + size_t request_size = requests.back().second; + requests.pop_back(); + byte magic = (request_size & 0xFF) | 1; + for (size_t k = 0; k < request_size; ++k) { + ASSERT_EQ(reinterpret_cast<const byte*>(obj)[k], magic); + } + ASSERT_GE(los->Free(Thread::Current(), obj), request_size); + } + } + + size_t bytes_allocated = 0; + // Checks that the coalescing works. + mirror::Object* obj = los->Alloc(Thread::Current(), 100 * MB, &bytes_allocated); + EXPECT_TRUE(obj != NULL); + los->Free(Thread::Current(), obj); + + EXPECT_EQ(0U, los->GetBytesAllocated()); + EXPECT_EQ(0U, los->GetObjectsAllocated()); + delete los; + } +} + TEST_F(SpaceTest, AllocAndFreeList) { DlMallocSpace* space(DlMallocSpace::Create("test", 4 * MB, 16 * MB, 16 * MB, NULL)); ASSERT_TRUE(space != NULL); @@ -207,7 +276,9 @@ TEST_F(SpaceTest, AllocAndFreeList) { // Succeeds, fits without adjusting the max allowed footprint. mirror::Object* lots_of_objects[1024]; for (size_t i = 0; i < arraysize(lots_of_objects); i++) { - lots_of_objects[i] = space->Alloc(self, 16, NULL); + size_t allocation_size = 0; + lots_of_objects[i] = space->Alloc(self, 16, &allocation_size); + EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i])); EXPECT_TRUE(lots_of_objects[i] != NULL); } @@ -219,7 +290,9 @@ TEST_F(SpaceTest, AllocAndFreeList) { // Succeeds, fits by adjusting the max allowed footprint. for (size_t i = 0; i < arraysize(lots_of_objects); i++) { - lots_of_objects[i] = space->AllocWithGrowth(self, 1024, NULL); + size_t allocation_size = 0; + lots_of_objects[i] = space->AllocWithGrowth(self, 1024, &allocation_size); + EXPECT_EQ(allocation_size, space->AllocationSize(lots_of_objects[i])); EXPECT_TRUE(lots_of_objects[i] != NULL); } @@ -230,11 +303,6 @@ TEST_F(SpaceTest, AllocAndFreeList) { } } -static size_t test_rand() { - // TODO: replace this with something random yet deterministic - return rand(); -} - void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(DlMallocSpace* space, intptr_t object_size, int round, size_t growth_limit) { if (((object_size > 0 && object_size >= static_cast<intptr_t>(growth_limit))) || @@ -267,6 +335,7 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(DlMallocSpace* space, intptr size_t last_object = 0; // last object for which allocation succeeded size_t amount_allocated = 0; // amount of space allocated Thread* self = Thread::Current(); + size_t rand_seed = 123456789; for (size_t i = 0; i < max_objects; i++) { size_t alloc_fails = 0; // number of failed allocations size_t max_fails = 30; // number of times we fail allocation before giving up @@ -275,22 +344,24 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(DlMallocSpace* space, intptr if (object_size > 0) { alloc_size = object_size; } else { - alloc_size = test_rand() % static_cast<size_t>(-object_size); + alloc_size = test_rand(&rand_seed) % static_cast<size_t>(-object_size); if (alloc_size < 8) { alloc_size = 8; } } mirror::Object* object; + size_t bytes_allocated = 0; if (round <= 1) { - object = space->Alloc(self, alloc_size, NULL); + object = space->Alloc(self, alloc_size, &bytes_allocated); } else { - object = space->AllocWithGrowth(self, alloc_size, NULL); + object = space->AllocWithGrowth(self, alloc_size, &bytes_allocated); } footprint = mspace_footprint(mspace); EXPECT_GE(space->Size(), footprint); // invariant if (object != NULL) { // allocation succeeded lots_of_objects.get()[i] = object; size_t allocation_size = space->AllocationSize(object); + EXPECT_EQ(bytes_allocated, allocation_size); if (object_size > 0) { EXPECT_GE(allocation_size, static_cast<size_t>(object_size)); } else { @@ -360,10 +431,11 @@ void SpaceTest::SizeFootPrintGrowthLimitAndTrimBody(DlMallocSpace* space, intptr // All memory was released, try a large allocation to check freed memory is being coalesced mirror::Object* large_object; size_t three_quarters_space = (growth_limit / 2) + (growth_limit / 4); + size_t bytes_allocated = 0; if (round <= 1) { - large_object = space->Alloc(self, three_quarters_space, NULL); + large_object = space->Alloc(self, three_quarters_space, &bytes_allocated); } else { - large_object = space->AllocWithGrowth(self, three_quarters_space, NULL); + large_object = space->AllocWithGrowth(self, three_quarters_space, &bytes_allocated); } EXPECT_TRUE(large_object != NULL); |