/* * Copyright (C) 2016-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SpaceTimeMutatorScheduler.h" #include "JSCInlines.h" namespace JSC { // The scheduler will often make decisions based on state that is in flux. It will be fine so // long as multiple uses of the same value all see the same value. We wouldn't get this for free, // since our need to modularize the calculation results in a tendency to access the same mutable // field in Heap multiple times, and to access the current time multiple times. class SpaceTimeMutatorScheduler::Snapshot { public: Snapshot(SpaceTimeMutatorScheduler& scheduler) { m_now = MonotonicTime::now(); m_bytesAllocatedThisCycle = scheduler.bytesAllocatedThisCycleImpl(); } MonotonicTime now() const { return m_now; } double bytesAllocatedThisCycle() const { return m_bytesAllocatedThisCycle; } private: MonotonicTime m_now; double m_bytesAllocatedThisCycle; }; SpaceTimeMutatorScheduler::SpaceTimeMutatorScheduler(Heap& heap) : m_heap(heap) , m_period(Seconds::fromMilliseconds(Options::concurrentGCPeriodMS())) { } SpaceTimeMutatorScheduler::~SpaceTimeMutatorScheduler() { } MutatorScheduler::State SpaceTimeMutatorScheduler::state() const { return m_state; } void SpaceTimeMutatorScheduler::beginCollection() { RELEASE_ASSERT(m_state == Normal); m_state = Stopped; m_startTime = MonotonicTime::now(); m_bytesAllocatedThisCycleAtTheBeginning = m_heap.m_bytesAllocatedThisCycle; m_bytesAllocatedThisCycleAtTheEnd = Options::concurrentGCMaxHeadroom() * std::max(m_bytesAllocatedThisCycleAtTheBeginning, m_heap.m_maxEdenSize); } void SpaceTimeMutatorScheduler::didStop() { RELEASE_ASSERT(m_state == Stopped || m_state == Resumed); m_state = Stopped; } void SpaceTimeMutatorScheduler::willResume() { RELEASE_ASSERT(m_state == Stopped || m_state == Resumed); m_state = Resumed; } void SpaceTimeMutatorScheduler::didExecuteConstraints() { // If we execute constraints, we want to forgive the GC for all of the time it had stopped the // world for in this increment. This hack is empirically better than every other heuristic I // tried, because it just means that the GC is happy to pause for longer when it's dealing // with things that don't play well with concurrency. // FIXME: The feels so wrong but benchmarks so good. // https://bugs.webkit.org/show_bug.cgi?id=166833 m_startTime = MonotonicTime::now(); } MonotonicTime SpaceTimeMutatorScheduler::timeToStop() { switch (m_state) { case Normal: return MonotonicTime::infinity(); case Stopped: return MonotonicTime::now(); case Resumed: { Snapshot snapshot(*this); if (!shouldBeResumed(snapshot)) return snapshot.now(); return snapshot.now() - elapsedInPeriod(snapshot) + m_period; } } RELEASE_ASSERT_NOT_REACHED(); return MonotonicTime(); } MonotonicTime SpaceTimeMutatorScheduler::timeToResume() { switch (m_state) { case Normal: case Resumed: return MonotonicTime::now(); case Stopped: { Snapshot snapshot(*this); if (shouldBeResumed(snapshot)) return snapshot.now(); return snapshot.now() - elapsedInPeriod(snapshot) + m_period * collectorUtilization(snapshot); } } RELEASE_ASSERT_NOT_REACHED(); return MonotonicTime(); } void SpaceTimeMutatorScheduler::log() { ASSERT(Options::logGC()); Snapshot snapshot(*this); dataLog( "a=", format("%.0lf", bytesSinceBeginningOfCycle(snapshot) / 1024), "kb ", "hf=", format("%.3lf", headroomFullness(snapshot)), " ", "mu=", format("%.3lf", mutatorUtilization(snapshot)), " "); } void SpaceTimeMutatorScheduler::endCollection() { m_state = Normal; m_startTime = MonotonicTime::now(); } double SpaceTimeMutatorScheduler::bytesAllocatedThisCycleImpl() { return m_heap.m_bytesAllocatedThisCycle; } double SpaceTimeMutatorScheduler::bytesSinceBeginningOfCycle(const Snapshot& snapshot) { return snapshot.bytesAllocatedThisCycle() - m_bytesAllocatedThisCycleAtTheBeginning; } double SpaceTimeMutatorScheduler::maxHeadroom() { return m_bytesAllocatedThisCycleAtTheEnd - m_bytesAllocatedThisCycleAtTheBeginning; } double SpaceTimeMutatorScheduler::headroomFullness(const Snapshot& snapshot) { double result = bytesSinceBeginningOfCycle(snapshot) / maxHeadroom(); // headroomFullness can be NaN and other interesting things if // bytesAllocatedThisCycleAtTheBeginning is zero. We see that in debug tests. This code // defends against all floating point dragons. if (!(result >= 0)) result = 0; if (!(result <= 1)) result = 1; return result; } double SpaceTimeMutatorScheduler::mutatorUtilization(const Snapshot& snapshot) { double mutatorUtilization = 1 - headroomFullness(snapshot); // Scale the mutator utilization into the permitted window. mutatorUtilization = Options::minimumMutatorUtilization() + mutatorUtilization * ( Options::maximumMutatorUtilization() - Options::minimumMutatorUtilization()); return mutatorUtilization; } double SpaceTimeMutatorScheduler::collectorUtilization(const Snapshot& snapshot) { return 1 - mutatorUtilization(snapshot); } Seconds SpaceTimeMutatorScheduler::elapsedInPeriod(const Snapshot& snapshot) { return (snapshot.now() - m_startTime) % m_period; } double SpaceTimeMutatorScheduler::phase(const Snapshot& snapshot) { return elapsedInPeriod(snapshot) / m_period; } bool SpaceTimeMutatorScheduler::shouldBeResumed(const Snapshot& snapshot) { return phase(snapshot) > collectorUtilization(snapshot); } } // namespace JSC