StochasticSpaceTimeMutatorScheduler.cpp   [plain text]


/*
 * 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<double>(m_bytesAllocatedThisCycleAtTheBeginning, m_heap.m_maxEdenSize);
    
    if (Options::logGC())
        dataLog("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);
    
    if (Options::logGC())
        dataLog("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