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