1 // Copyright 2016 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "src/zone/accounting-allocator.h" 6 7 #include <cstdlib> 8 9 #if V8_LIBC_BIONIC 10 #include <malloc.h> // NOLINT 11 #endif 12 13 namespace v8 { 14 namespace internal { 15 16 AccountingAllocator::AccountingAllocator() : unused_segments_mutex_() { 17 static const size_t kDefaultBucketMaxSize = 5; 18 19 memory_pressure_level_.SetValue(MemoryPressureLevel::kNone); 20 std::fill(unused_segments_heads_, unused_segments_heads_ + kNumberBuckets, 21 nullptr); 22 std::fill(unused_segments_sizes_, unused_segments_sizes_ + kNumberBuckets, 0); 23 std::fill(unused_segments_max_sizes_, 24 unused_segments_max_sizes_ + kNumberBuckets, kDefaultBucketMaxSize); 25 } 26 27 AccountingAllocator::~AccountingAllocator() { ClearPool(); } 28 29 void AccountingAllocator::MemoryPressureNotification( 30 MemoryPressureLevel level) { 31 memory_pressure_level_.SetValue(level); 32 33 if (level != MemoryPressureLevel::kNone) { 34 ClearPool(); 35 } 36 } 37 38 void AccountingAllocator::ConfigureSegmentPool(const size_t max_pool_size) { 39 // The sum of the bytes of one segment of each size. 40 static const size_t full_size = (size_t(1) << (kMaxSegmentSizePower + 1)) - 41 (size_t(1) << kMinSegmentSizePower); 42 size_t fits_fully = max_pool_size / full_size; 43 44 base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_); 45 46 // We assume few zones (less than 'fits_fully' many) to be active at the same 47 // time. When zones grow regularly, they will keep requesting segments of 48 // increasing size each time. Therefore we try to get as many segments with an 49 // equal number of segments of each size as possible. 50 // The remaining space is used to make more room for an 'incomplete set' of 51 // segments beginning with the smaller ones. 52 // This code will work best if the max_pool_size is a multiple of the 53 // full_size. If max_pool_size is no sum of segment sizes the actual pool 54 // size might be smaller then max_pool_size. Note that no actual memory gets 55 // wasted though. 56 // TODO(heimbuef): Determine better strategy generating a segment sizes 57 // distribution that is closer to real/benchmark usecases and uses the given 58 // max_pool_size more efficiently. 59 size_t total_size = fits_fully * full_size; 60 61 for (size_t power = 0; power < kNumberBuckets; ++power) { 62 if (total_size + (size_t(1) << (power + kMinSegmentSizePower)) <= 63 max_pool_size) { 64 unused_segments_max_sizes_[power] = fits_fully + 1; 65 total_size += size_t(1) << power; 66 } else { 67 unused_segments_max_sizes_[power] = fits_fully; 68 } 69 } 70 } 71 72 Segment* AccountingAllocator::GetSegment(size_t bytes) { 73 Segment* result = GetSegmentFromPool(bytes); 74 if (result == nullptr) { 75 result = AllocateSegment(bytes); 76 if (result != nullptr) { 77 result->Initialize(bytes); 78 } 79 } 80 81 return result; 82 } 83 84 Segment* AccountingAllocator::AllocateSegment(size_t bytes) { 85 void* memory = malloc(bytes); 86 if (memory) { 87 base::AtomicWord current = 88 base::NoBarrier_AtomicIncrement(¤t_memory_usage_, bytes); 89 base::AtomicWord max = base::NoBarrier_Load(&max_memory_usage_); 90 while (current > max) { 91 max = base::NoBarrier_CompareAndSwap(&max_memory_usage_, max, current); 92 } 93 } 94 return reinterpret_cast<Segment*>(memory); 95 } 96 97 void AccountingAllocator::ReturnSegment(Segment* segment) { 98 segment->ZapContents(); 99 100 if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) { 101 FreeSegment(segment); 102 } else if (!AddSegmentToPool(segment)) { 103 FreeSegment(segment); 104 } 105 } 106 107 void AccountingAllocator::FreeSegment(Segment* memory) { 108 base::NoBarrier_AtomicIncrement( 109 ¤t_memory_usage_, -static_cast<base::AtomicWord>(memory->size())); 110 memory->ZapHeader(); 111 free(memory); 112 } 113 114 size_t AccountingAllocator::GetCurrentMemoryUsage() const { 115 return base::NoBarrier_Load(¤t_memory_usage_); 116 } 117 118 size_t AccountingAllocator::GetMaxMemoryUsage() const { 119 return base::NoBarrier_Load(&max_memory_usage_); 120 } 121 122 size_t AccountingAllocator::GetCurrentPoolSize() const { 123 return base::NoBarrier_Load(¤t_pool_size_); 124 } 125 126 Segment* AccountingAllocator::GetSegmentFromPool(size_t requested_size) { 127 if (requested_size > (1 << kMaxSegmentSizePower)) { 128 return nullptr; 129 } 130 131 size_t power = kMinSegmentSizePower; 132 while (requested_size > (static_cast<size_t>(1) << power)) power++; 133 134 DCHECK_GE(power, kMinSegmentSizePower + 0); 135 power -= kMinSegmentSizePower; 136 137 Segment* segment; 138 { 139 base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_); 140 141 segment = unused_segments_heads_[power]; 142 143 if (segment != nullptr) { 144 unused_segments_heads_[power] = segment->next(); 145 segment->set_next(nullptr); 146 147 unused_segments_sizes_[power]--; 148 base::NoBarrier_AtomicIncrement( 149 ¤t_pool_size_, -static_cast<base::AtomicWord>(segment->size())); 150 } 151 } 152 153 if (segment) { 154 DCHECK_GE(segment->size(), requested_size); 155 } 156 return segment; 157 } 158 159 bool AccountingAllocator::AddSegmentToPool(Segment* segment) { 160 size_t size = segment->size(); 161 162 if (size >= (1 << (kMaxSegmentSizePower + 1))) return false; 163 164 if (size < (1 << kMinSegmentSizePower)) return false; 165 166 size_t power = kMaxSegmentSizePower; 167 168 while (size < (static_cast<size_t>(1) << power)) power--; 169 170 DCHECK_GE(power, kMinSegmentSizePower + 0); 171 power -= kMinSegmentSizePower; 172 173 { 174 base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_); 175 176 if (unused_segments_sizes_[power] >= unused_segments_max_sizes_[power]) { 177 return false; 178 } 179 180 segment->set_next(unused_segments_heads_[power]); 181 unused_segments_heads_[power] = segment; 182 base::NoBarrier_AtomicIncrement(¤t_pool_size_, size); 183 unused_segments_sizes_[power]++; 184 } 185 186 return true; 187 } 188 189 void AccountingAllocator::ClearPool() { 190 base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_); 191 192 for (size_t power = 0; power <= kMaxSegmentSizePower - kMinSegmentSizePower; 193 power++) { 194 Segment* current = unused_segments_heads_[power]; 195 while (current) { 196 Segment* next = current->next(); 197 FreeSegment(current); 198 current = next; 199 } 200 unused_segments_heads_[power] = nullptr; 201 } 202 } 203 204 } // namespace internal 205 } // namespace v8 206