1/*
2 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SpaceTimeMutatorScheduler.h"
28
29#include "JSCInlines.h"
30
31namespace JSC {
32
33// The scheduler will often make decisions based on state that is in flux. It will be fine so
34// long as multiple uses of the same value all see the same value. We wouldn't get this for free,
35// since our need to modularize the calculation results in a tendency to access the same mutable
36// field in Heap multiple times, and to access the current time multiple times.
37class SpaceTimeMutatorScheduler::Snapshot {
38public:
39 Snapshot(SpaceTimeMutatorScheduler& scheduler)
40 {
41 m_now = MonotonicTime::now();
42 m_bytesAllocatedThisCycle = scheduler.bytesAllocatedThisCycleImpl();
43 }
44
45 MonotonicTime now() const { return m_now; }
46
47 double bytesAllocatedThisCycle() const { return m_bytesAllocatedThisCycle; }
48
49private:
50 MonotonicTime m_now;
51 double m_bytesAllocatedThisCycle;
52};
53
54SpaceTimeMutatorScheduler::SpaceTimeMutatorScheduler(Heap& heap)
55 : m_heap(heap)
56 , m_period(Seconds::fromMilliseconds(Options::concurrentGCPeriodMS()))
57{
58}
59
60SpaceTimeMutatorScheduler::~SpaceTimeMutatorScheduler()
61{
62}
63
64MutatorScheduler::State SpaceTimeMutatorScheduler::state() const
65{
66 return m_state;
67}
68
69void SpaceTimeMutatorScheduler::beginCollection()
70{
71 RELEASE_ASSERT(m_state == Normal);
72 m_state = Stopped;
73 m_startTime = MonotonicTime::now();
74
75 m_bytesAllocatedThisCycleAtTheBeginning = m_heap.m_bytesAllocatedThisCycle;
76 m_bytesAllocatedThisCycleAtTheEnd =
77 Options::concurrentGCMaxHeadroom() *
78 std::max<double>(m_bytesAllocatedThisCycleAtTheBeginning, m_heap.m_maxEdenSize);
79}
80
81void SpaceTimeMutatorScheduler::didStop()
82{
83 RELEASE_ASSERT(m_state == Stopped || m_state == Resumed);
84 m_state = Stopped;
85}
86
87void SpaceTimeMutatorScheduler::willResume()
88{
89 RELEASE_ASSERT(m_state == Stopped || m_state == Resumed);
90 m_state = Resumed;
91}
92
93void SpaceTimeMutatorScheduler::didExecuteConstraints()
94{
95 // If we execute constraints, we want to forgive the GC for all of the time it had stopped the
96 // world for in this increment. This hack is empirically better than every other heuristic I
97 // tried, because it just means that the GC is happy to pause for longer when it's dealing
98 // with things that don't play well with concurrency.
99 // FIXME: The feels so wrong but benchmarks so good.
100 // https://bugs.webkit.org/show_bug.cgi?id=166833
101 m_startTime = MonotonicTime::now();
102}
103
104MonotonicTime SpaceTimeMutatorScheduler::timeToStop()
105{
106 switch (m_state) {
107 case Normal:
108 return MonotonicTime::infinity();
109 case Stopped:
110 return MonotonicTime::now();
111 case Resumed: {
112 Snapshot snapshot(*this);
113 if (!shouldBeResumed(snapshot))
114 return snapshot.now();
115 return snapshot.now() - elapsedInPeriod(snapshot) + m_period;
116 } }
117
118 RELEASE_ASSERT_NOT_REACHED();
119 return MonotonicTime();
120}
121
122MonotonicTime SpaceTimeMutatorScheduler::timeToResume()
123{
124 switch (m_state) {
125 case Normal:
126 case Resumed:
127 return MonotonicTime::now();
128 case Stopped: {
129 Snapshot snapshot(*this);
130 if (shouldBeResumed(snapshot))
131 return snapshot.now();
132 return snapshot.now() - elapsedInPeriod(snapshot) + m_period * collectorUtilization(snapshot);
133 } }
134
135 RELEASE_ASSERT_NOT_REACHED();
136 return MonotonicTime();
137}
138
139void SpaceTimeMutatorScheduler::log()
140{
141 ASSERT(Options::logGC());
142 Snapshot snapshot(*this);
143 dataLog(
144 "a=", format("%.0lf", bytesSinceBeginningOfCycle(snapshot) / 1024), "kb ",
145 "hf=", format("%.3lf", headroomFullness(snapshot)), " ",
146 "mu=", format("%.3lf", mutatorUtilization(snapshot)), " ");
147}
148
149void SpaceTimeMutatorScheduler::endCollection()
150{
151 m_state = Normal;
152 m_startTime = MonotonicTime::now();
153}
154
155double SpaceTimeMutatorScheduler::bytesAllocatedThisCycleImpl()
156{
157 return m_heap.m_bytesAllocatedThisCycle;
158}
159
160double SpaceTimeMutatorScheduler::bytesSinceBeginningOfCycle(const Snapshot& snapshot)
161{
162 return snapshot.bytesAllocatedThisCycle() - m_bytesAllocatedThisCycleAtTheBeginning;
163}
164
165double SpaceTimeMutatorScheduler::maxHeadroom()
166{
167 return m_bytesAllocatedThisCycleAtTheEnd - m_bytesAllocatedThisCycleAtTheBeginning;
168}
169
170double SpaceTimeMutatorScheduler::headroomFullness(const Snapshot& snapshot)
171{
172 double result = bytesSinceBeginningOfCycle(snapshot) / maxHeadroom();
173
174 // headroomFullness can be NaN and other interesting things if
175 // bytesAllocatedThisCycleAtTheBeginning is zero. We see that in debug tests. This code
176 // defends against all floating point dragons.
177
178 if (!(result >= 0))
179 result = 0;
180 if (!(result <= 1))
181 result = 1;
182
183 return result;
184}
185
186double SpaceTimeMutatorScheduler::mutatorUtilization(const Snapshot& snapshot)
187{
188 double mutatorUtilization = 1 - headroomFullness(snapshot);
189
190 // Scale the mutator utilization into the permitted window.
191 mutatorUtilization =
192 Options::minimumMutatorUtilization() +
193 mutatorUtilization * (
194 Options::maximumMutatorUtilization() -
195 Options::minimumMutatorUtilization());
196
197 return mutatorUtilization;
198}
199
200double SpaceTimeMutatorScheduler::collectorUtilization(const Snapshot& snapshot)
201{
202 return 1 - mutatorUtilization(snapshot);
203}
204
205Seconds SpaceTimeMutatorScheduler::elapsedInPeriod(const Snapshot& snapshot)
206{
207 return (snapshot.now() - m_startTime) % m_period;
208}
209
210double SpaceTimeMutatorScheduler::phase(const Snapshot& snapshot)
211{
212 return elapsedInPeriod(snapshot) / m_period;
213}
214
215bool SpaceTimeMutatorScheduler::shouldBeResumed(const Snapshot& snapshot)
216{
217 return phase(snapshot) > collectorUtilization(snapshot);
218}
219
220} // namespace JSC
221
222