/* * Copyright (C) 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 "StochasticSpaceTimeMutatorScheduler.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 StochasticSpaceTimeMutatorScheduler::Snapshot { public: Snapshot(StochasticSpaceTimeMutatorScheduler& 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; }; StochasticSpaceTimeMutatorScheduler::StochasticSpaceTimeMutatorScheduler(Heap& heap) : m_heap(heap) , m_minimumPause(Seconds::fromMilliseconds(Options::minimumGCPauseMS())) , m_pauseScale(Options::gcPauseScale()) { } StochasticSpaceTimeMutatorScheduler::~StochasticSpaceTimeMutatorScheduler() { } MutatorScheduler::State StochasticSpaceTimeMutatorScheduler::state() const { return m_state; } void StochasticSpaceTimeMutatorScheduler::beginCollection() { RELEASE_ASSERT(m_state == Normal); m_state = Stopped; m_bytesAllocatedThisCycleAtTheBeginning = m_heap.m_bytesAllocatedThisCycle; m_bytesAllocatedThisCycleAtTheEnd = Options::concurrentGCMaxHeadroom() * std::max(m_bytesAllocatedThisCycleAtTheBeginning, m_heap.m_maxEdenSize); dataLogIf(Options::logGC(), "ca=", m_bytesAllocatedThisCycleAtTheBeginning / 1024, "kb h=", (m_bytesAllocatedThisCycleAtTheEnd - m_bytesAllocatedThisCycleAtTheBeginning) / 1024, "kb "); m_beforeConstraints = MonotonicTime::now(); } void StochasticSpaceTimeMutatorScheduler::didStop() { RELEASE_ASSERT(m_state == Stopped || m_state == Resumed); m_state = Stopped; } void StochasticSpaceTimeMutatorScheduler::willResume() { RELEASE_ASSERT(m_state == Stopped || m_state == Resumed); m_state = Resumed; } void StochasticSpaceTimeMutatorScheduler::didReachTermination() { m_beforeConstraints = MonotonicTime::now(); } void StochasticSpaceTimeMutatorScheduler::didExecuteConstraints() { Snapshot snapshot(*this); Seconds constraintExecutionDuration = snapshot.now() - m_beforeConstraints; m_targetPause = std::max( constraintExecutionDuration * m_pauseScale, m_minimumPause); dataLogIf(Options::logGC(), "tp=", m_targetPause.milliseconds(), "ms "); m_plannedResumeTime = snapshot.now() + m_targetPause; } void StochasticSpaceTimeMutatorScheduler::synchronousDrainingDidStall() { Snapshot snapshot(*this); double resumeProbability = mutatorUtilization(snapshot); if (resumeProbability < Options::epsilonMutatorUtilization()) { m_plannedResumeTime = MonotonicTime::infinity(); return; } bool shouldResume = m_random.get() < resumeProbability; if (shouldResume) { m_plannedResumeTime = snapshot.now(); return; } m_plannedResumeTime = snapshot.now() + m_targetPause; } MonotonicTime StochasticSpaceTimeMutatorScheduler::timeToStop() { switch (m_state) { case Normal: return MonotonicTime::infinity(); case Stopped: return MonotonicTime::now(); case Resumed: { // Once we're running, we keep going unless we run out of headroom. Snapshot snapshot(*this); if (mutatorUtilization(snapshot) < Options::epsilonMutatorUtilization()) return MonotonicTime::now(); return MonotonicTime::infinity(); } } RELEASE_ASSERT_NOT_REACHED(); return MonotonicTime(); } MonotonicTime StochasticSpaceTimeMutatorScheduler::timeToResume() { switch (m_state) { case Normal: case Resumed: return MonotonicTime::now(); case Stopped: return m_plannedResumeTime; } RELEASE_ASSERT_NOT_REACHED(); return MonotonicTime(); } void StochasticSpaceTimeMutatorScheduler::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 StochasticSpaceTimeMutatorScheduler::endCollection() { m_state = Normal; } double StochasticSpaceTimeMutatorScheduler::bytesAllocatedThisCycleImpl() { return m_heap.m_bytesAllocatedThisCycle; } double StochasticSpaceTimeMutatorScheduler::bytesSinceBeginningOfCycle(const Snapshot& snapshot) { return snapshot.bytesAllocatedThisCycle() - m_bytesAllocatedThisCycleAtTheBeginning; } double StochasticSpaceTimeMutatorScheduler::maxHeadroom() { return m_bytesAllocatedThisCycleAtTheEnd - m_bytesAllocatedThisCycleAtTheBeginning; } double StochasticSpaceTimeMutatorScheduler::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 StochasticSpaceTimeMutatorScheduler::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; } } // namespace JSC