Skip to content

Commit 20ed510

Browse files
committed
improve GC
GC until memory is reduced by 5% increase memory limit when needed
1 parent 2e6a26c commit 20ed510

File tree

2 files changed

+120
-50
lines changed

2 files changed

+120
-50
lines changed

crates/turbo-tasks-memory/src/gc.rs

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ const MAX_TASKS_PER_OLD_GENERATION: usize = 200_000;
7272
const PERCENTAGE_TO_COLLECT: usize = 30;
7373
const TASK_BASE_MEMORY_USAGE: usize = 1_000;
7474
const TASK_BASE_COMPUTE_DURATION_IN_MICROS: u64 = 1_000;
75-
pub const PERCENTAGE_TARGET_MEMORY: usize = 88;
76-
pub const PERCENTAGE_IDLE_TARGET_MEMORY: usize = 75;
75+
pub const PERCENTAGE_MIN_TARGET_MEMORY: usize = 70;
76+
pub const PERCENTAGE_MAX_TARGET_MEMORY: usize = 75;
77+
pub const PERCENTAGE_MIN_IDLE_TARGET_MEMORY: usize = 55;
78+
pub const PERCENTAGE_MAX_IDLE_TARGET_MEMORY: usize = 60;
79+
pub const MAX_GC_STEPS: usize = 100;
7780

7881
struct OldGeneration {
7982
tasks: Vec<TaskId>,
@@ -82,6 +85,7 @@ struct OldGeneration {
8285

8386
#[derive(Default)]
8487
struct ProcessGenerationResult {
88+
old_generations: usize,
8589
priority: Option<GcPriority>,
8690
content_dropped_count: usize,
8791
unloaded_count: usize,
@@ -224,18 +228,22 @@ impl GcQueue {
224228
&self,
225229
backend: &MemoryBackend,
226230
turbo_tasks: &dyn TurboTasksBackendApi<MemoryBackend>,
227-
) -> ProcessGenerationResult {
228-
let old_generation = {
231+
) -> Option<ProcessGenerationResult> {
232+
let old_generation_info = {
229233
let guard = &mut self.generations.lock();
230-
guard.pop_back()
234+
let len = guard.len();
235+
guard.pop_back().map(|g| (g, len))
231236
};
232-
let Some(OldGeneration {
233-
mut tasks,
234-
generation,
235-
}) = old_generation
237+
let Some((
238+
OldGeneration {
239+
mut tasks,
240+
generation,
241+
},
242+
old_generations,
243+
)) = old_generation_info
236244
else {
237245
// No old generation to process
238-
return ProcessGenerationResult::default();
246+
return None;
239247
};
240248
// Check all tasks for the correct generation
241249
let mut indices = Vec::with_capacity(tasks.len());
@@ -254,7 +262,10 @@ impl GcQueue {
254262

255263
if indices.is_empty() {
256264
// No valid tasks in old generation to process
257-
return ProcessGenerationResult::default();
265+
return Some(ProcessGenerationResult {
266+
old_generations,
267+
..ProcessGenerationResult::default()
268+
});
258269
}
259270

260271
// Sorting based on sort_by_cached_key from std lib
@@ -329,23 +340,24 @@ impl GcQueue {
329340
});
330341
}
331342

332-
ProcessGenerationResult {
343+
Some(ProcessGenerationResult {
344+
old_generations,
333345
priority: Some(max_priority),
334346
content_dropped_count,
335347
unloaded_count,
336348
already_unloaded_count,
337-
}
349+
})
338350
}
339351

340-
/// Run garbage collection on the queue.
352+
/// Run garbage collection on the queue. Returns true, if some progress has
353+
/// been made. Returns the number of old generations.
341354
pub fn run_gc(
342355
&self,
343356
backend: &MemoryBackend,
344357
turbo_tasks: &dyn TurboTasksBackendApi<MemoryBackend>,
345-
) -> Option<(GcPriority, usize)> {
358+
) -> Option<usize> {
346359
let span = tracing::trace_span!(
347-
parent: None,
348-
"garbage collection",
360+
"garbage collection step",
349361
priority = Empty,
350362
deactivations_count = Empty,
351363
content_dropped_count = Empty,
@@ -358,23 +370,27 @@ impl GcQueue {
358370
count: deactivations_count,
359371
} = self.process_deactivations(backend, turbo_tasks);
360372

361-
let ProcessGenerationResult {
373+
if let Some(ProcessGenerationResult {
374+
old_generations,
362375
priority,
363376
content_dropped_count,
364377
unloaded_count,
365378
already_unloaded_count,
366-
} = self.process_old_generation(backend, turbo_tasks);
379+
}) = self.process_old_generation(backend, turbo_tasks)
380+
{
381+
span.record("deactivations_count", deactivations_count);
382+
span.record("content_dropped_count", content_dropped_count);
383+
span.record("unloaded_count", unloaded_count);
384+
span.record("already_unloaded_count", already_unloaded_count);
385+
if let Some(priority) = &priority {
386+
span.record("priority", debug(priority));
387+
} else {
388+
span.record("priority", "");
389+
}
367390

368-
span.record("deactivations_count", deactivations_count);
369-
span.record("content_dropped_count", content_dropped_count);
370-
span.record("unloaded_count", unloaded_count);
371-
span.record("already_unloaded_count", already_unloaded_count);
372-
if let Some(priority) = &priority {
373-
span.record("priority", debug(priority));
391+
Some(old_generations)
374392
} else {
375-
span.record("priority", "");
393+
(deactivations_count > 0).then_some(0)
376394
}
377-
378-
priority.map(|p| (p, content_dropped_count))
379395
}
380396
}

crates/turbo-tasks-memory/src/memory_backend.rs

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
num::NonZeroU32,
77
pin::Pin,
88
sync::{
9-
atomic::{AtomicBool, Ordering},
9+
atomic::{AtomicBool, AtomicUsize, Ordering},
1010
Arc,
1111
},
1212
time::Duration,
@@ -31,7 +31,10 @@ use turbo_tasks::{
3131

3232
use crate::{
3333
edges_set::{TaskEdge, TaskEdgesSet},
34-
gc::{GcQueue, PERCENTAGE_IDLE_TARGET_MEMORY, PERCENTAGE_TARGET_MEMORY},
34+
gc::{
35+
GcQueue, MAX_GC_STEPS, PERCENTAGE_MAX_IDLE_TARGET_MEMORY, PERCENTAGE_MAX_TARGET_MEMORY,
36+
PERCENTAGE_MIN_IDLE_TARGET_MEMORY, PERCENTAGE_MIN_TARGET_MEMORY,
37+
},
3538
output::Output,
3639
task::{ReadCellError, Task, DEPENDENCIES_TO_TRACK},
3740
task_statistics::TaskStatisticsApi,
@@ -47,7 +50,7 @@ pub struct MemoryBackend {
4750
backend_job_id_factory: IdFactoryWithReuse<BackendJobId>,
4851
task_cache:
4952
DashMap<Arc<PreHashed<PersistentTaskType>>, TaskId, BuildHasherDefault<PassThroughHash>>,
50-
memory_limit: usize,
53+
memory_limit: AtomicUsize,
5154
gc_queue: Option<GcQueue>,
5255
idle_gc_active: AtomicBool,
5356
task_statistics: TaskStatisticsApi,
@@ -70,7 +73,7 @@ impl MemoryBackend {
7073
(std::thread::available_parallelism().map_or(1, usize::from) * 32)
7174
.next_power_of_two(),
7275
),
73-
memory_limit,
76+
memory_limit: AtomicUsize::new(memory_limit),
7477
gc_queue: (memory_limit != usize::MAX).then(GcQueue::new),
7578
idle_gc_active: AtomicBool::new(false),
7679
task_statistics: TaskStatisticsApi::default(),
@@ -141,27 +144,78 @@ impl MemoryBackend {
141144
) -> bool {
142145
if let Some(gc_queue) = &self.gc_queue {
143146
let mut did_something = false;
144-
loop {
145-
let mem_limit = self.memory_limit;
146-
147-
let usage = turbo_tasks_malloc::TurboMalloc::memory_usage();
148-
let target = if idle {
149-
mem_limit * PERCENTAGE_IDLE_TARGET_MEMORY / 100
147+
let mut remaining_generations = 0;
148+
let mut mem_limit = self.memory_limit.load(Ordering::Relaxed);
149+
let mut span = None;
150+
'outer: loop {
151+
let mut collected_generations = 0;
152+
let (min, max) = if idle {
153+
(
154+
mem_limit * PERCENTAGE_MIN_IDLE_TARGET_MEMORY / 100,
155+
mem_limit * PERCENTAGE_MAX_IDLE_TARGET_MEMORY / 100,
156+
)
150157
} else {
151-
mem_limit * PERCENTAGE_TARGET_MEMORY / 100
158+
(
159+
mem_limit * PERCENTAGE_MIN_TARGET_MEMORY / 100,
160+
mem_limit * PERCENTAGE_MAX_TARGET_MEMORY / 100,
161+
)
152162
};
153-
if usage < target {
154-
return did_something;
155-
}
156-
157-
let collected = gc_queue.run_gc(self, turbo_tasks);
158-
159-
// Collecting less than 100 tasks is not worth it
160-
if !collected.map_or(false, |(_, count)| count > 100) {
161-
return true;
163+
let mut target = max;
164+
let mut counter = 0;
165+
loop {
166+
let usage = turbo_tasks_malloc::TurboMalloc::memory_usage();
167+
if usage < target {
168+
return did_something;
169+
}
170+
target = min;
171+
if span.is_none() {
172+
span =
173+
Some(tracing::trace_span!(parent: None, "garbage collection", usage));
174+
}
175+
176+
let progress = gc_queue.run_gc(self, turbo_tasks);
177+
178+
if progress.is_some() {
179+
did_something = true;
180+
}
181+
182+
if let Some(g) = progress {
183+
remaining_generations = g;
184+
if g > 0 {
185+
collected_generations += 1;
186+
}
187+
}
188+
189+
counter += 1;
190+
if counter > MAX_GC_STEPS
191+
|| collected_generations > remaining_generations
192+
|| progress.is_none()
193+
{
194+
let new_mem_limit = mem_limit * 4 / 3;
195+
if self
196+
.memory_limit
197+
.compare_exchange(
198+
mem_limit,
199+
new_mem_limit,
200+
Ordering::Relaxed,
201+
Ordering::Relaxed,
202+
)
203+
.is_ok()
204+
{
205+
println!(
206+
"Ineffective GC, increasing memory limit {} MB -> {} MB",
207+
mem_limit / 1024 / 1024,
208+
new_mem_limit / 1024 / 1024
209+
);
210+
mem_limit = new_mem_limit;
211+
} else {
212+
mem_limit = self.memory_limit.load(Ordering::Relaxed);
213+
}
214+
continue 'outer;
215+
}
216+
217+
did_something = true;
162218
}
163-
164-
did_something = true;
165219
}
166220
}
167221
false

0 commit comments

Comments
 (0)