1 /*
2 * Copyright (c) 2021 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
16 #include "runtime/mem/gc/gc_trigger.h"
17
18 #include <atomic>
19
20 #include "libpandabase/macros.h"
21 #include "runtime/include/runtime.h"
22 #include "runtime/include/runtime_options.h"
23 #include "runtime/include/panda_vm.h"
24 #include "utils/logger.h"
25
26 namespace panda::mem {
27
28 static constexpr size_t PERCENT_100 = 100;
29
30 GCTrigger::~GCTrigger() = default;
31
GCTriggerHeap(MemStatsType * mem_stats)32 GCTriggerHeap::GCTriggerHeap(MemStatsType *mem_stats) : mem_stats_(mem_stats) {}
33
GCTriggerHeap(MemStatsType * mem_stats,size_t min_heap_size,uint8_t percent_threshold,size_t min_extra_size,size_t max_extra_size,uint32_t skip_gc_times)34 GCTriggerHeap::GCTriggerHeap(MemStatsType *mem_stats, size_t min_heap_size, uint8_t percent_threshold,
35 size_t min_extra_size, size_t max_extra_size, uint32_t skip_gc_times)
36 : mem_stats_(mem_stats), skip_gc_count_(skip_gc_times)
37 {
38 percent_threshold_ = percent_threshold;
39 min_extra_size_ = min_extra_size;
40 max_extra_size_ = max_extra_size;
41 // If we have min_heap_size < 100, we get false positives in IsGcTriggered, since we divide by 100 first
42 ASSERT(min_heap_size >= 100);
43 target_footprint_.store((min_heap_size / PERCENT_100) * percent_threshold_, std::memory_order_relaxed);
44 LOG(DEBUG, GC_TRIGGER) << "GCTriggerHeap created, min heap size " << min_heap_size << ", percent threshold "
45 << percent_threshold << ", min_extra_size " << min_extra_size << ", max_extra_size "
46 << max_extra_size;
47 }
48
SetMinTargetFootprint(size_t target_size)49 void GCTriggerHeap::SetMinTargetFootprint(size_t target_size)
50 {
51 LOG(DEBUG, GC_TRIGGER) << "SetTempTargetFootprint target_footprint = " << target_size;
52 min_target_footprint_ = target_size;
53 target_footprint_.store(target_size, std::memory_order_relaxed);
54 }
55
RestoreMinTargetFootprint()56 void GCTriggerHeap::RestoreMinTargetFootprint()
57 {
58 min_target_footprint_ = DEFAULT_MIN_TARGET_FOOTPRINT;
59 }
60
ComputeNewTargetFootprint(const GCTask & task,size_t heap_size_before_gc,size_t heap_size)61 void GCTriggerHeap::ComputeNewTargetFootprint(const GCTask &task, size_t heap_size_before_gc, size_t heap_size)
62 {
63 GC *gc = Thread::GetCurrent()->GetVM()->GetGC();
64 if (gc->IsGenerational() && task.reason_ == GCTaskCause::YOUNG_GC_CAUSE) {
65 // we don't want to update heap-trigger on young-gc
66 return;
67 }
68 // Note: divide by 100 first to avoid overflow
69 size_t delta = (heap_size / PERCENT_100) * percent_threshold_;
70
71 if (heap_size > heap_size_before_gc) { // heap increased corresponding with previous gc
72 delta = std::min(delta, max_extra_size_);
73 } else {
74 // if heap was squeeze from 200mb to 100mb we want to set a target to 150mb, not just 100mb*percent_threshold_
75 delta = std::max(delta, (heap_size_before_gc - heap_size) / 2);
76 }
77 delta = std::max(delta, min_extra_size_);
78 size_t target = heap_size + delta;
79
80 target_footprint_.store(target, std::memory_order_relaxed);
81
82 LOG(DEBUG, GC_TRIGGER) << "ComputeNewTargetFootprint target_footprint = " << target;
83 }
84
IsGcTriggered()85 bool GCTriggerHeap::IsGcTriggered()
86 {
87 if (skip_gc_count_ > 0) {
88 skip_gc_count_--;
89 return false;
90 }
91 size_t bytes_in_heap = mem_stats_->GetFootprintHeap();
92 if (UNLIKELY(bytes_in_heap >= target_footprint_.load(std::memory_order_relaxed))) {
93 LOG(DEBUG, GC_TRIGGER) << "GCTriggerHeap triggered";
94 return true;
95 }
96 return false;
97 }
98
CreateGCTrigger(MemStatsType * mem_stats,const GCTriggerConfig & config,InternalAllocatorPtr allocator)99 GCTrigger *CreateGCTrigger(MemStatsType *mem_stats, const GCTriggerConfig &config, InternalAllocatorPtr allocator)
100 {
101 std::string_view gc_trigger_type = config.GetGCTriggerType();
102 uint32_t skip_gc_times = config.GetSkipStartupGcCount();
103
104 constexpr size_t DEFAULT_HEAP_SIZE = 8_MB;
105 constexpr uint8_t DEFAULT_PERCENT_THRESHOLD = 10;
106 auto trigger_type = GCTriggerType::INVALID_TRIGGER;
107 if (gc_trigger_type == "heap-trigger-test") {
108 trigger_type = GCTriggerType::HEAP_TRIGGER_TEST;
109 } else if (gc_trigger_type == "heap-trigger") {
110 trigger_type = GCTriggerType::HEAP_TRIGGER;
111 } else if (gc_trigger_type == "debug") {
112 trigger_type = GCTriggerType::DEBUG;
113 } else if (gc_trigger_type == "no-gc-for-start-up") {
114 trigger_type = GCTriggerType::NO_GC_FOR_START_UP;
115 }
116 GCTrigger *ret {nullptr};
117 switch (trigger_type) { // NOLINT(hicpp-multiway-paths-covered)
118 case GCTriggerType::HEAP_TRIGGER_TEST:
119 ret = allocator->New<GCTriggerHeap>(mem_stats);
120 break;
121 case GCTriggerType::HEAP_TRIGGER:
122 ret = allocator->New<GCTriggerHeap>(mem_stats, DEFAULT_HEAP_SIZE, DEFAULT_PERCENT_THRESHOLD,
123 config.GetMinExtraHeapSize(), config.GetMaxExtraHeapSize());
124 break;
125 case GCTriggerType::NO_GC_FOR_START_UP:
126 ret = allocator->New<GCTriggerHeap>(mem_stats, DEFAULT_HEAP_SIZE, DEFAULT_PERCENT_THRESHOLD,
127 config.GetMinExtraHeapSize(), config.GetMaxExtraHeapSize(),
128 skip_gc_times);
129 break;
130 case GCTriggerType::DEBUG:
131 ret = allocator->New<GCTriggerDebug>(config.GetDebugStart());
132 break;
133 default:
134 LOG(FATAL, GC) << "Wrong GCTrigger type";
135 break;
136 }
137 return ret;
138 }
139
GCStarted(size_t heap_size)140 void GCTriggerHeap::GCStarted([[maybe_unused]] size_t heap_size) {}
141
GCFinished(const GCTask & task,size_t heap_size_before_gc,size_t heap_size)142 void GCTriggerHeap::GCFinished(const GCTask &task, size_t heap_size_before_gc, size_t heap_size)
143 {
144 ComputeNewTargetFootprint(task, heap_size_before_gc, heap_size);
145 }
146
GetTargetFootprint()147 size_t GCTriggerHeap::GetTargetFootprint()
148 {
149 return target_footprint_.load(std::memory_order_relaxed);
150 }
151
GCTriggerDebug(uint64_t debug_start)152 GCTriggerDebug::GCTriggerDebug(uint64_t debug_start) : debug_start_(debug_start)
153 {
154 LOG(DEBUG, GC_TRIGGER) << "GCTriggerDebug created";
155 }
156
IsGcTriggered()157 bool GCTriggerDebug::IsGcTriggered()
158 {
159 bool ret = false;
160 static std::atomic<uint64_t> counter = 0;
161 LOG(DEBUG, GC_TRIGGER) << "GCTriggerDebug counter " << counter;
162 if (counter >= debug_start_) {
163 LOG(DEBUG, GC_TRIGGER) << "GCTriggerDebug triggered";
164 ret = true;
165 }
166 counter++;
167 return ret;
168 }
169
GCStarted(size_t heap_size)170 void GCTriggerDebug::GCStarted([[maybe_unused]] size_t heap_size) {}
171
GCFinished(const GCTask & task,size_t heap_size_before_gc,size_t heap_size)172 void GCTriggerDebug::GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc,
173 [[maybe_unused]] size_t heap_size)
174 {
175 }
176
GetTargetFootprint()177 size_t GCTriggerDebug::GetTargetFootprint()
178 {
179 return 0;
180 }
181
182 } // namespace panda::mem
183