• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #ifndef PANDA_RUNTIME_MEM_GC_GC_THRESHOLD_H
16 #define PANDA_RUNTIME_MEM_GC_GC_THRESHOLD_H
17 
18 #include <cstddef>
19 #include <cstdint>
20 #include <string_view>
21 
22 #include "libpandabase/macros.h"
23 #include "libpandabase/utils/ring_buffer.h"
24 #include "runtime/mem/gc/gc.h"
25 
26 namespace ark {
27 
28 class RuntimeOptions;
29 
30 namespace test {
31 class GCTriggerTest;
32 }  // namespace test
33 
34 namespace mem {
35 
36 enum class GCTriggerType {
37     INVALID_TRIGGER,
38     HEAP_TRIGGER_TEST,        // TRIGGER with low thresholds for tests
39     HEAP_TRIGGER,             // Standard TRIGGER with production ready thresholds
40     ADAPTIVE_HEAP_TRIGGER,    // TRIGGER with adaptive strategy for heap increasing
41     NO_GC_FOR_START_UP,       // A non-production strategy, TRIGGER GC after the app starts up
42     TRIGGER_HEAP_OCCUPANCY,   // Trigger full GC by heap size threshold
43     DEBUG,                    // Debug TRIGGER which always returns true
44     DEBUG_NEVER,              // Trigger for testing which never triggers (young-gc can still trigger), for test purpose
45     ON_NTH_ALLOC,             // Triggers GC on n-th allocation
46     PAUSE_TIME_GOAL_TRIGGER,  // Triggers concurrent marking by heap size threshold and ask GC to change eden size to
47                               // satisfy pause time goal
48     GCTRIGGER_LAST = ON_NTH_ALLOC
49 };
50 
51 class GCTriggerConfig {
52 public:
53     GCTriggerConfig(const RuntimeOptions &options, panda_file::SourceLang lang);
54 
GetGCTriggerType()55     std::string_view GetGCTriggerType() const
56     {
57         return gcTriggerType_;
58     }
59 
GetDebugStart()60     uint64_t GetDebugStart() const
61     {
62         return debugStart_;
63     }
64 
GetPercentThreshold()65     uint32_t GetPercentThreshold() const
66     {
67         return percentThreshold_;
68     }
69 
GetAdaptiveMultiplier()70     uint32_t GetAdaptiveMultiplier() const
71     {
72         return adaptiveMultiplier_;
73     }
74 
GetMinExtraHeapSize()75     size_t GetMinExtraHeapSize() const
76     {
77         return minExtraHeapSize_;
78     }
79 
GetMaxExtraHeapSize()80     size_t GetMaxExtraHeapSize() const
81     {
82         return maxExtraHeapSize_;
83     }
84 
GetMaxTriggerPercent()85     uint32_t GetMaxTriggerPercent() const
86     {
87         return maxTriggerPercent_;
88     }
89 
GetSkipStartupGcCount()90     uint32_t GetSkipStartupGcCount() const
91     {
92         return skipStartupGcCount_;
93     }
94 
IsUseNthAllocTrigger()95     bool IsUseNthAllocTrigger() const
96     {
97         return useNthAllocTrigger_;
98     }
99 
100 private:
101     std::string gcTriggerType_;
102     uint64_t debugStart_;
103     uint32_t percentThreshold_;
104     uint32_t adaptiveMultiplier_;
105     size_t minExtraHeapSize_;
106     size_t maxExtraHeapSize_;
107     uint32_t maxTriggerPercent_;
108     uint32_t skipStartupGcCount_;
109     bool useNthAllocTrigger_;
110 };
111 
112 class GCTrigger : public GCListener {
113 public:
114     GCTrigger() = default;
115     ~GCTrigger() override = default;
116 
117     NO_COPY_SEMANTIC(GCTrigger);
118     NO_MOVE_SEMANTIC(GCTrigger);
119 
120     /**
121      * @brief Checks if GC required
122      * @return returns true if GC should be executed
123      */
124     virtual void TriggerGcIfNeeded(GC *gc) = 0;
125     virtual GCTriggerType GetType() const = 0;
SetMinTargetFootprint(size_t heapSize)126     virtual void SetMinTargetFootprint([[maybe_unused]] size_t heapSize) {}
RestoreMinTargetFootprint()127     virtual void RestoreMinTargetFootprint() {}
128 
129 private:
130     friend class GC;
131 };
132 
133 /// Triggers when heap increased by predefined %
134 class GCTriggerHeap : public GCTrigger {
135 public:
136     explicit GCTriggerHeap(MemStatsType *memStats, HeapSpace *heapSpace);
137     explicit GCTriggerHeap(MemStatsType *memStats, HeapSpace *heapSpace, size_t minHeapSize, uint8_t percentThreshold,
138                            size_t minExtraSize, size_t maxExtraSize, uint32_t skipGcTimes = 0);
139 
GetType()140     GCTriggerType GetType() const override
141     {
142         return GCTriggerType::HEAP_TRIGGER;
143     }
144 
145     void TriggerGcIfNeeded(GC *gc) override;
146     void GCStarted(const GCTask &task, size_t heapSize) override;
147     void GCFinished(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize) override;
148     void SetMinTargetFootprint(size_t targetSize) override;
149     void RestoreMinTargetFootprint() override;
150     void ComputeNewTargetFootprint(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize);
151 
152     static constexpr uint8_t DEFAULT_PERCENTAGE_THRESHOLD = 20;
153 
154 protected:
155     // NOTE(dtrubenkov): change to the proper value when all triggers will be enabled
156     static constexpr size_t MIN_HEAP_SIZE_FOR_TRIGGER = 512;
157     static constexpr size_t DEFAULT_MIN_TARGET_FOOTPRINT = 256;
158     static constexpr size_t DEFAULT_MIN_EXTRA_HEAP_SIZE = 32;      // For heap-trigger-test
159     static constexpr size_t DEFAULT_MAX_EXTRA_HEAP_SIZE = 512_KB;  // For heap-trigger-test
160 
161     virtual size_t ComputeTarget(size_t heapSizeBeforeGc, size_t heapSize);
162 
163     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
164     std::atomic<size_t> targetFootprint_ {MIN_HEAP_SIZE_FOR_TRIGGER};
165 
166     /**
167      * We'll trigger if heap increased by delta, delta = heap_size_after_last_gc * percent_threshold_ %
168      * And the constraint on delta is: min_extra_size_ <= delta <= max_extra_size_
169      */
170     uint8_t percentThreshold_ {DEFAULT_PERCENTAGE_THRESHOLD};  // NOLINT(misc-non-private-member-variables-in-classes)
171     size_t minExtraSize_ {DEFAULT_MIN_EXTRA_HEAP_SIZE};        // NOLINT(misc-non-private-member-variables-in-classes)
172     size_t maxExtraSize_ {DEFAULT_MAX_EXTRA_HEAP_SIZE};        // NOLINT(misc-non-private-member-variables-in-classes)
173 
174 private:
175     HeapSpace *heapSpace_ {nullptr};
176     size_t minTargetFootprint_ {DEFAULT_MIN_TARGET_FOOTPRINT};
177     MemStatsType *memStats_;
178     uint8_t skipGcCount_ {0};
179 
180     friend class ark::test::GCTriggerTest;
181 };
182 
183 /// Triggers when heap increased by adaptive strategy
184 class GCAdaptiveTriggerHeap : public GCTriggerHeap {
185 public:
186     GCAdaptiveTriggerHeap(MemStatsType *memStats, HeapSpace *heapSpace, size_t minHeapSize, uint8_t percentThreshold,
187                           uint32_t adaptiveMultiplier, size_t minExtraSize, size_t maxExtraSize,
188                           uint32_t skipGcTimes = 0);
189     NO_COPY_SEMANTIC(GCAdaptiveTriggerHeap);
190     NO_MOVE_SEMANTIC(GCAdaptiveTriggerHeap);
191     ~GCAdaptiveTriggerHeap() override = default;
192 
GetType()193     GCTriggerType GetType() const override
194     {
195         return GCTriggerType::ADAPTIVE_HEAP_TRIGGER;
196     }
197 
198 private:
199     static constexpr uint32_t DEFAULT_INCREASE_MULTIPLIER = 3U;
200     // We save last RECENT_THRESHOLDS_COUNT thresholds for detect too often triggering
201     static constexpr size_t RECENT_THRESHOLDS_COUNT = 3;
202 
203     size_t ComputeTarget(size_t heapSizeBeforeGc, size_t heapSize) override;
204 
205     RingBuffer<size_t, RECENT_THRESHOLDS_COUNT> recentTargetThresholds_;
206     uint32_t adaptiveMultiplier_ {DEFAULT_INCREASE_MULTIPLIER};
207 
208     friend class ark::test::GCTriggerTest;
209 };
210 
211 /// Trigger always returns true after given start
212 class GCTriggerDebug : public GCTrigger {
213 public:
214     explicit GCTriggerDebug(uint64_t debugStart, HeapSpace *heapSpace);
215 
GetType()216     GCTriggerType GetType() const override
217     {
218         return GCTriggerType::DEBUG;
219     }
220 
221     void TriggerGcIfNeeded(GC *gc) override;
222     void GCStarted(const GCTask &task, size_t heapSize) override;
223     void GCFinished(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize) override;
224 
225 private:
226     HeapSpace *heapSpace_ {nullptr};
227     uint64_t debugStart_ = 0;
228 };
229 
230 class GCTriggerHeapOccupancy : public GCTrigger {
231 public:
232     explicit GCTriggerHeapOccupancy(HeapSpace *heapSpace, uint32_t maxTriggerPercent);
233 
GetType()234     GCTriggerType GetType() const override
235     {
236         return GCTriggerType::TRIGGER_HEAP_OCCUPANCY;
237     }
238 
239     void TriggerGcIfNeeded(GC *gc) override;
240 
241     void GCStarted(const GCTask &task, [[maybe_unused]] size_t heapSize) override;
242     void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
243                     [[maybe_unused]] size_t heapSize) override;
244 
245 private:
246     HeapSpace *heapSpace_ {nullptr};
247     double maxTriggerPercent_ = 0.0;
248 };
249 
250 class GCNeverTrigger : public GCTrigger {
251 public:
GetType()252     GCTriggerType GetType() const override
253     {
254         return GCTriggerType::DEBUG_NEVER;
255     }
256 
TriggerGcIfNeeded(GC * gc)257     void TriggerGcIfNeeded([[maybe_unused]] GC *gc) override {}
258 
GCStarted(const GCTask & task,size_t heapSize)259     void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override {}
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)260     void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
261                     [[maybe_unused]] size_t heapSize) override
262     {
263     }
264 };
265 
266 class SchedGCOnNthAllocTrigger : public GCTrigger {
267 public:
268     explicit SchedGCOnNthAllocTrigger(GCTrigger *origin);
269     ~SchedGCOnNthAllocTrigger() override;
270 
GetType()271     GCTriggerType GetType() const override
272     {
273         return GCTriggerType::ON_NTH_ALLOC;
274     }
275 
IsTriggered()276     bool IsTriggered() const
277     {
278         return isTriggered_;
279     }
280 
281     void TriggerGcIfNeeded(GC *gc) override;
282     void ScheduleGc(GCTaskCause cause, uint32_t counter);
GCStarted(const GCTask & task,size_t heapSize)283     void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override {}
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)284     void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
285                     [[maybe_unused]] size_t heapSize) override
286     {
287     }
288 
289     NO_COPY_SEMANTIC(SchedGCOnNthAllocTrigger);
290     NO_MOVE_SEMANTIC(SchedGCOnNthAllocTrigger);
291 
292 private:
293     GCTrigger *origin_;
294     std::atomic<uint32_t> counter_ = 0;
295     GCTaskCause cause_ = GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE;
296     bool isTriggered_ = false;
297 };
298 
299 class PauseTimeGoalTrigger : public GCTrigger {
300 public:
301     PauseTimeGoalTrigger(MemStatsType *memStats, size_t minHeapSize, uint8_t percentThreshold, size_t minExtraSize,
302                          size_t maxExtraSize);
303     NO_COPY_SEMANTIC(PauseTimeGoalTrigger);
304     NO_MOVE_SEMANTIC(PauseTimeGoalTrigger);
305     ~PauseTimeGoalTrigger() override = default;
306 
GetType()307     GCTriggerType GetType() const override
308     {
309         return GCTriggerType::PAUSE_TIME_GOAL_TRIGGER;
310     }
311 
312     void GCFinished(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize) override;
313 
314     void TriggerGcIfNeeded(GC *gc) override;
315 
GetTargetFootprint()316     size_t GetTargetFootprint() const
317     {
318         // Atomic with relaxed order reason: data race with target_footprint_ with no synchronization or ordering
319         // constraints imposed on other reads or writes
320         return targetFootprint_.load(std::memory_order_relaxed);
321     }
322 
323 private:
324     size_t ComputeTarget(size_t heapSizeBeforeGc, size_t heapSize);
325     MemStatsType *memStats_;
326     uint8_t percentThreshold_;
327     size_t minExtraSize_;
328     size_t maxExtraSize_;
329     std::atomic<size_t> targetFootprint_;
330     std::atomic<bool> startConcurrentMarking_ {false};
331 };
332 
333 GCTrigger *CreateGCTrigger(MemStatsType *memStats, HeapSpace *heapSpace, const GCTriggerConfig &config,
334                            InternalAllocatorPtr allocator);
335 
336 }  // namespace mem
337 }  // namespace ark
338 
339 #endif  // PANDA_RUNTIME_MEM_GC_GC_THRESHOLD_H
340