Changeset 217456 in webkit
- Timestamp:
- May 25, 2017 4:11:24 PM (7 years ago)
- Location:
- trunk/Source/bmalloc
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/bmalloc/ChangeLog
r217000 r217456 1 2017-05-25 Geoffrey Garen <ggaren@apple.com> and Michael Saboff <msaboff@apple.com> 2 3 bmalloc: scavenger runs too much on JetStream 4 https://bugs.webkit.org/show_bug.cgi?id=172373 5 6 Reviewed by Geoffrey Garen. 7 8 Instruments says that JetStream on macOS spends about 3% of its time in 9 madvise. 10 11 In <https://bugs.webkit.org/show_bug.cgi?id=160098>, Ben saw some 12 evidence that madvise was the reason that switching to bmalloc for 13 DFG::Node allocations was a slowdown the first time around. 14 15 In <https://bugs.webkit.org/show_bug.cgi?id=172124>, Michael saw that 16 scavening policy can affect JetStream. 17 18 Intuitively, it seems wrong for the heap to idle shrink during hardcore 19 benchmarking. 20 21 The strategy here is to back off in response to any heap growth event, 22 and to wait 2s instead of 0.5s for heap growth to take place -- but we 23 scavenge immediately in response to critical memory pressure, to avoid 24 jetsam. 25 26 One hole in this strategy is that a workload with a perfectly 27 unfragmented heap that allocates and deallocates ~16kB every 2s will 28 never shrink its heap. This doesn't seem to be a problem in practice. 29 30 This looks like a 2% - 4% speedup on JetStream on Mac Pro and MacBook Air. 31 32 * bmalloc/AsyncTask.h: 33 (bmalloc::AsyncTask::willRun): 34 (bmalloc::AsyncTask::willRunSoon): 35 (bmalloc::Function>::AsyncTask): 36 (bmalloc::Function>::run): 37 (bmalloc::Function>::runSoon): 38 (bmalloc::Function>::threadRunLoop): 39 (bmalloc::Function>::runSlowCase): Deleted. Added a "run soon" state 40 so that execution delay is modeled directly instead of implicitly 41 through sleep events. This enables the Heap to issue a "run now" event 42 at any moment in response ot memory pressure. 43 44 * bmalloc/Heap.cpp: 45 (bmalloc::Heap::Heap): Don't call into our own API -- that's a layering 46 violation. 47 48 (bmalloc::Heap::updateMemoryInUseParameters): No need for 49 m_scavengeSleepDuration anymore. 50 51 (bmalloc::Heap::concurrentScavenge): Added a back-off policy when the 52 heap is growing. 53 (bmalloc::Heap::scavenge): 54 55 (bmalloc::Heap::scavengeSmallPages): 56 (bmalloc::Heap::scavengeLargeObjects): Don't try to give up in the middle 57 of a scavenge event. Our new backoff policy supplants that design. Also, 58 it's easier to profile and understand scavenging behavior if it always 59 runs to completion once started. 60 61 (bmalloc::Heap::scheduleScavenger): 62 (bmalloc::Heap::scheduleScavengerIfUnderMemoryPressure): Added a 63 synchronous amortized check for memory pressure. This check has the 64 benefit that it runs immediately during high rates of heap activity, 65 so we can detect memory pressure right away and wake the scavenger 66 instead of waiting for the scavenger to wake up. 67 68 (bmalloc::Heap::allocateSmallPage): 69 (bmalloc::Heap::deallocateSmallLine): 70 (bmalloc::Heap::splitAndAllocate): 71 (bmalloc::Heap::tryAllocateLarge): 72 (bmalloc::Heap::shrinkLarge): 73 (bmalloc::Heap::deallocateLarge): 74 * bmalloc/Heap.h: 75 (bmalloc::Heap::isUnderMemoryPressure): 76 * bmalloc/Sizes.h: 77 * bmalloc/VMHeap.h: 78 (bmalloc::VMHeap::deallocateSmallPage): 79 * bmalloc/bmalloc.h: 80 (bmalloc::api::scavenge): Updated for API changes above. 81 1 82 2017-05-17 Michael Saboff <msaboff@apple.com> 2 83 -
trunk/Source/bmalloc/bmalloc/AsyncTask.h
r208562 r217456 30 30 #include "Inline.h" 31 31 #include "Mutex.h" 32 #include "Sizes.h" 32 33 #include <atomic> 33 34 #include <condition_variable> … … 41 42 AsyncTask(Object&, const Function&); 42 43 ~AsyncTask(); 43 44 45 bool willRun() { return m_state == State::Run; } 44 46 void run(); 45 47 48 bool willRunSoon() { return m_state > State::Sleep; } 49 void runSoon(); 50 46 51 private: 47 enum State { Sleeping, Running, RunRequested};48 52 enum class State { Sleep, Run, RunSoon }; 53 49 54 void runSlowCase(); 50 55 void runSoonSlowCase(); 56 51 57 static void threadEntryPoint(AsyncTask*); 52 58 void threadRunLoop(); … … 65 71 template<typename Object, typename Function> 66 72 AsyncTask<Object, Function>::AsyncTask(Object& object, const Function& function) 67 : m_state( Running)73 : m_state(State::Sleep) 68 74 , m_condition() 69 75 , m_thread(std::thread(&AsyncTask::threadEntryPoint, this)) … … 82 88 83 89 template<typename Object, typename Function> 84 inlinevoid AsyncTask<Object, Function>::run()90 void AsyncTask<Object, Function>::run() 85 91 { 86 if (m_state == RunRequested) 87 return; 88 runSlowCase(); 92 m_state = State::Run; 93 94 std::lock_guard<Mutex> lock(m_conditionMutex); 95 m_condition.notify_all(); 89 96 } 90 97 91 98 template<typename Object, typename Function> 92 NO_INLINE void AsyncTask<Object, Function>::runSlowCase()99 void AsyncTask<Object, Function>::runSoon() 93 100 { 94 State oldState = m_state.exchange(RunRequested); 95 if (oldState == RunRequested || oldState == Running) 96 return; 97 98 BASSERT(oldState == Sleeping); 101 m_state = State::RunSoon; 102 99 103 std::lock_guard<Mutex> lock(m_conditionMutex); 100 104 m_condition.notify_all(); … … 116 120 // This loop ratchets downward from most active to least active state. While 117 121 // we ratchet downward, any other thread may reset our state. 118 122 119 123 // We require any state change while we are sleeping to signal to our 120 124 // condition variable and wake us up. 121 125 122 126 while (1) { 123 State expectedState = RunRequested; 124 if (m_state.compare_exchange_weak(expectedState, Running)) 125 (m_object.*m_function)(); 126 127 expectedState = Running; 128 if (m_state.compare_exchange_weak(expectedState, Sleeping)) { 127 if (m_state == State::Sleep) { 129 128 std::unique_lock<Mutex> lock(m_conditionMutex); 130 m_condition.wait(lock, [&]() { return m_state != S leeping; });129 m_condition.wait(lock, [&]() { return m_state != State::Sleep; }); 131 130 } 131 132 if (m_state == State::RunSoon) { 133 std::unique_lock<Mutex> lock(m_conditionMutex); 134 m_condition.wait_for(lock, asyncTaskSleepDuration, [&]() { return m_state != State::RunSoon; }); 135 } 136 137 m_state = State::Sleep; 138 (m_object.*m_function)(); 132 139 } 133 140 } -
trunk/Source/bmalloc/bmalloc/Heap.cpp
r216763 r217456 69 69 m_pressureHandlerDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_CRITICAL, queue); 70 70 dispatch_source_set_event_handler(m_pressureHandlerDispatchSource, ^{ 71 api::scavenge(); 71 std::unique_lock<StaticMutex> lock(PerProcess<Heap>::mutex()); 72 scavenge(lock); 72 73 }); 73 74 dispatch_resume(m_pressureHandlerDispatchSource); … … 145 146 double percentInUse = static_cast<double>(m_memoryFootprint) / static_cast<double>(m_maxAvailableMemory); 146 147 m_percentAvailableMemoryInUse = std::min(percentInUse, 1.0); 147 148 double percentFree = 1.0 - m_percentAvailableMemoryInUse;149 double sleepInMS = 1200.0 * percentFree * percentFree - 100.0 * percentFree + 2.0;150 sleepInMS = std::max(std::min(sleepInMS, static_cast<double>(maxScavengeSleepDuration.count())), 2.0);151 152 m_scavengeSleepDuration = std::chrono::milliseconds(static_cast<long long>(sleepInMS));153 148 } 154 149 #endif … … 156 151 void Heap::concurrentScavenge() 157 152 { 153 std::unique_lock<StaticMutex> lock(PerProcess<Heap>::mutex()); 154 158 155 #if BOS(DARWIN) 159 156 pthread_set_qos_class_self_np(m_requestedScavengerThreadQOSClass, 0); 160 157 #endif 161 158 162 std::unique_lock<StaticMutex> lock(PerProcess<Heap>::mutex()); 163 164 scavenge(lock, Async); 165 166 #if BPLATFORM(IOS) 167 updateMemoryInUseParameters(); 168 #endif 169 } 170 171 void Heap::scavenge(std::unique_lock<StaticMutex>& lock, ScavengeMode scavengeMode) 172 { 173 m_isAllocatingPages.fill(false); 174 m_isAllocatingLargePages = false; 175 176 if (scavengeMode == Async) 177 sleep(lock, m_scavengeSleepDuration); 178 179 scavengeSmallPages(lock, scavengeMode); 180 scavengeLargeObjects(lock, scavengeMode); 181 } 182 183 void Heap::scavengeSmallPages(std::unique_lock<StaticMutex>& lock, ScavengeMode scavengeMode) 159 if (m_isGrowing && !isUnderMemoryPressure()) { 160 m_isGrowing = false; 161 m_scavenger.runSoon(); 162 return; 163 } 164 165 scavenge(lock); 166 } 167 168 void Heap::scavenge(std::unique_lock<StaticMutex>& lock) 169 { 170 scavengeSmallPages(lock); 171 scavengeLargeObjects(lock); 172 } 173 174 void Heap::scavengeSmallPages(std::unique_lock<StaticMutex>& lock) 184 175 { 185 176 for (size_t pageClass = 0; pageClass < pageClassCount; pageClass++) { … … 187 178 188 179 while (!smallPages.isEmpty()) { 189 if (m_isAllocatingPages[pageClass]) {190 m_scavenger.run();191 break;192 }193 194 180 SmallPage* page = smallPages.pop(); 195 m_vmHeap.deallocateSmallPage(lock, pageClass, page , scavengeMode);196 } 197 } 198 } 199 200 void Heap::scavengeLargeObjects(std::unique_lock<StaticMutex>& lock , ScavengeMode scavengeMode)181 m_vmHeap.deallocateSmallPage(lock, pageClass, page); 182 } 183 } 184 } 185 186 void Heap::scavengeLargeObjects(std::unique_lock<StaticMutex>& lock) 201 187 { 202 188 auto& ranges = m_largeFree.ranges(); 203 189 for (size_t i = ranges.size(); i-- > 0; i = std::min(i, ranges.size())) { 204 if (m_isAllocatingLargePages) {205 m_scavenger.run();206 break;207 }208 209 190 auto range = ranges.pop(i); 210 211 if (scavengeMode == Async) 212 lock.unlock(); 191 192 lock.unlock(); 213 193 vmDeallocatePhysicalPagesSloppy(range.begin(), range.size()); 214 if (scavengeMode == Async) 215 lock.lock(); 194 lock.lock(); 216 195 217 196 range.setPhysicalSize(0); … … 220 199 } 221 200 201 void Heap::scheduleScavengerIfUnderMemoryPressure(size_t bytes) 202 { 203 m_scavengerBytes += bytes; 204 if (m_scavengerBytes < scavengerBytesPerMemoryPressureCheck) 205 return; 206 207 m_scavengerBytes = 0; 208 209 if (m_scavenger.willRun()) 210 return; 211 212 if (!isUnderMemoryPressure()) 213 return; 214 215 m_isGrowing = false; 216 m_scavenger.run(); 217 } 218 219 void Heap::scheduleScavenger(size_t bytes) 220 { 221 scheduleScavengerIfUnderMemoryPressure(bytes); 222 223 if (m_scavenger.willRunSoon()) 224 return; 225 226 m_isGrowing = false; 227 m_scavenger.runSoon(); 228 } 229 222 230 SmallPage* Heap::allocateSmallPage(std::lock_guard<StaticMutex>& lock, size_t sizeClass) 223 231 { … … 225 233 return m_smallPagesWithFreeLines[sizeClass].popFront(); 226 234 235 m_isGrowing = true; 236 227 237 SmallPage* page = [&]() { 228 238 size_t pageClass = m_pageClasses[sizeClass]; … … 230 240 return m_smallPages[pageClass].pop(); 231 241 232 m_isAllocatingPages[pageClass] = true;233 242 scheduleScavengerIfUnderMemoryPressure(pageSize(pageClass)); 243 234 244 SmallPage* page = m_vmHeap.allocateSmallPage(lock, pageClass); 235 245 m_objectTypes.set(Chunk::get(page), ObjectType::Small); … … 260 270 m_smallPagesWithFreeLines[sizeClass].remove(page); 261 271 m_smallPages[pageClass].push(page); 262 263 m_scavenger.run();272 273 scheduleScavenger(pageSize(pageClass)); 264 274 } 265 275 … … 399 409 400 410 if (range.physicalSize() < range.size()) { 401 m_isAllocatingLargePages = true;402 411 scheduleScavengerIfUnderMemoryPressure(range.size()); 412 403 413 vmAllocatePhysicalPagesSloppy(range.begin() + range.physicalSize(), range.size() - range.physicalSize()); 404 414 range.setPhysicalSize(range.size()); … … 421 431 BASSERT(isPowerOfTwo(alignment)); 422 432 433 m_isGrowing = true; 434 423 435 size_t roundedSize = size ? roundUpToMultipleOf(largeAlignment, size) : largeAlignment; 424 436 if (roundedSize < size) // Check for overflow … … 469 481 splitAndAllocate(range, alignment, newSize); 470 482 471 m_scavenger.run();483 scheduleScavenger(size); 472 484 } 473 485 … … 477 489 m_largeFree.add(LargeRange(object, size, size)); 478 490 479 m_scavenger.run();491 scheduleScavenger(size); 480 492 } 481 493 -
trunk/Source/bmalloc/bmalloc/Heap.h
r217000 r217456 71 71 void shrinkLarge(std::lock_guard<StaticMutex>&, const Range&, size_t); 72 72 73 void scavenge(std::unique_lock<StaticMutex>&, ScavengeMode); 74 75 #if BPLATFORM(IOS) 73 void scavenge(std::unique_lock<StaticMutex>&); 74 76 75 size_t memoryFootprint(); 77 76 double percentAvailableMemoryInUse(); 78 #endif 79 77 bool isUnderMemoryPressure(); 78 80 79 #if BOS(DARWIN) 81 80 void setScavengerThreadQOSClass(qos_class_t overrideClass) { m_requestedScavengerThreadQOSClass = overrideClass; } … … 111 110 LargeRange splitAndAllocate(LargeRange&, size_t alignment, size_t); 112 111 112 void scheduleScavenger(size_t); 113 void scheduleScavengerIfUnderMemoryPressure(size_t); 114 113 115 void concurrentScavenge(); 114 void scavengeSmallPages(std::unique_lock<StaticMutex>& , ScavengeMode);115 void scavengeLargeObjects(std::unique_lock<StaticMutex>& , ScavengeMode);116 116 void scavengeSmallPages(std::unique_lock<StaticMutex>&); 117 void scavengeLargeObjects(std::unique_lock<StaticMutex>&); 118 117 119 #if BPLATFORM(IOS) 118 120 void updateMemoryInUseParameters(); … … 131 133 Map<Chunk*, ObjectType, ChunkHash> m_objectTypes; 132 134 133 s td::array<bool, pageClassCount> m_isAllocatingPages;134 bool m_is AllocatingLargePages;135 135 size_t m_scavengerBytes { 0 }; 136 bool m_isGrowing { false }; 137 136 138 AsyncTask<Heap, decltype(&Heap::concurrentScavenge)> m_scavenger; 137 139 138 140 Environment m_environment; 139 141 DebugHeap* m_debugHeap; 140 141 std::chrono::milliseconds m_scavengeSleepDuration = { maxScavengeSleepDuration };142 142 143 143 #if BPLATFORM(IOS) … … 171 171 } 172 172 173 inline bool Heap::isUnderMemoryPressure() 174 { 175 #if BPLATFORM(IOS) 176 return percentAvailableMemoryInUse() > memoryPressureThreshold; 177 #else 178 return false; 179 #endif 180 } 181 173 182 #if BPLATFORM(IOS) 174 183 inline size_t Heap::memoryFootprint() -
trunk/Source/bmalloc/bmalloc/Sizes.h
r216960 r217456 69 69 static const size_t bumpRangeCacheCapacity = 3; 70 70 71 static const std::chrono::milliseconds maxScavengeSleepDuration = std::chrono::milliseconds(512); 72 71 static const size_t scavengerBytesPerMemoryPressureCheck = 16 * MB; 72 static const double memoryPressureThreshold = 0.75; 73 74 static const std::chrono::milliseconds asyncTaskSleepDuration = std::chrono::milliseconds(2000); 75 73 76 static const size_t maskSizeClassCount = maskSizeClassMax / alignment; 74 77 -
trunk/Source/bmalloc/bmalloc/VMHeap.h
r215909 r217456 47 47 public: 48 48 SmallPage* allocateSmallPage(std::lock_guard<StaticMutex>&, size_t); 49 void deallocateSmallPage(std::unique_lock<StaticMutex>&, size_t, SmallPage* , ScavengeMode);49 void deallocateSmallPage(std::unique_lock<StaticMutex>&, size_t, SmallPage*); 50 50 51 51 LargeRange tryAllocateLargeChunk(std::lock_guard<StaticMutex>&, size_t alignment, size_t); … … 71 71 } 72 72 73 inline void VMHeap::deallocateSmallPage(std::unique_lock<StaticMutex>& lock, size_t pageClass, SmallPage* page , ScavengeMode scavengeMode)73 inline void VMHeap::deallocateSmallPage(std::unique_lock<StaticMutex>& lock, size_t pageClass, SmallPage* page) 74 74 { 75 if (scavengeMode == Async) 76 lock.unlock(); 75 lock.unlock(); 77 76 vmDeallocatePhysicalPagesSloppy(page->begin()->begin(), pageSize(pageClass)); 78 if (scavengeMode == Async) 79 lock.lock(); 77 lock.lock(); 80 78 81 79 m_smallPages[pageClass].push(page); -
trunk/Source/bmalloc/bmalloc/bmalloc.h
r216763 r217456 78 78 79 79 std::unique_lock<StaticMutex> lock(PerProcess<Heap>::mutex()); 80 PerProcess<Heap>::get()->scavenge(lock , Sync);80 PerProcess<Heap>::get()->scavenge(lock); 81 81 } 82 82
Note: See TracChangeset
for help on using the changeset viewer.