1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Statistics of GC runs are exposed in tasks.
use std::fmt;
use std::time::Duration;
/// Timings of GC operation.
pub struct GcStats {
/// Number of unreachable elements.
/// This is the number of objects that is destroyed during this GC run.
pub unreachable: usize,
/// Number of objects that is retained.
/// This is the number of objects, that the GC observed and found reachable.
pub retained: usize,
/// Time spent waiting for the GC lock to be available.
/// Only multi-thread GC will ever record time in here.
///
/// In order to prevent multiple GCs from running at the same time,
/// the task exclusion lock ensures only one can run at any given moment.
///
/// If this time is high, it probably means there's a lot of overlapping GC requests.
/// To reduce, you can place use [DeferGc][crate::DeferGc] in places where this happens.
/// Or you could make GCs be managed on a thread (using [GcTask::install_callback][crate::sync::GcTask::install_callback]).
pub task_lock: Duration,
/// Duration of the mark-sweep operation.
/// This operation separates all reachable objects from the unreachable objects.
pub mark_sweep: Duration,
/// Time spent doing the secondary mark-sweep,
/// where [Weak][crate::Weak] and [sync::Weak][crate::sync::Weak] are locked out of promotions.
///
/// This only applies to multi-thread operations, if weak pointers are enabled.
///
/// If there is a long time spent here (relative to [mark_sweep][GcStats::mark_sweep] duration),
/// it could mean there are a lot of weak pointer promotions happening.
/// Or maybe a single weak pointer got promoted, that causes a large number of dependent objects to suddenly become live.
pub mark_sweep_with_weak_lockout: Duration,
/// Time spent running destructors.
/// This time is spent outside the task lock, so another GC on the same generation can start and not cause issues.
///
/// If a lot of time is spent here, it could mean that there are many objects being destroyed.
///
/// Note that if the destructors start additional GCs,
/// those GCs will be counted against the [destructors][GcStats::destructors] duration.
/// If you want timings without those additional GCs,
/// you should use [GcTask::install_callback][crate::GcTask::install_callback]
/// or [sync::GcTask::install_callback][crate::sync::GcTask::install_callback]
/// to move those GC runs outside the destruction, by placing tasks on a job queue.
pub destructors: Duration,
}
impl GcStats {
/// Total time spent in the garbage collector.
#[inline]
pub fn total_time(&self) -> Duration {
self.task_lock + self.mark_sweep + self.mark_sweep_with_weak_lockout + self.destructors
}
/// The size of the generation, before the GC operation started.
#[inline]
pub const fn object_count_before_gc(&self) -> usize {
self.unreachable + self.retained
}
}
impl fmt::Debug for GcStats {
#[inline(never)]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("GcStats")
.field("object_count_before_gc", &self.object_count_before_gc())
.field("unreachable", &self.unreachable)
.field("retained", &self.retained)
.field("total_time", &self.total_time())
.field("task_lock", &self.task_lock)
.field("mark_sweep", &self.mark_sweep)
.field(
"mark_sweep_with_weak_lockout",
&self.mark_sweep_with_weak_lockout,
)
.field("destructors", &self.destructors)
.finish()
}
}