Skip to content

Commit 9633bae

Browse files
committed
Extract messy tree handling out of profiling code
1 parent 0f099ea commit 9633bae

File tree

6 files changed

+118
-150
lines changed

6 files changed

+118
-150
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_arena/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ impl<T> Arena<T> {
9696
pub const fn new() -> Arena<T> {
9797
Arena { data: Vec::new() }
9898
}
99+
pub fn clear(&mut self) {
100+
self.data.clear();
101+
}
99102

100103
pub fn len(&self) -> usize {
101104
self.data.len()

crates/ra_prof/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ publish = false
99
doctest = false
1010

1111
[dependencies]
12+
ra_arena = { path = "../ra_arena" }
1213
once_cell = "1.3.1"
1314
backtrace = { version = "0.3.44", optional = true }
1415

crates/ra_prof/src/hprof.rs

Lines changed: 29 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Simple hierarchical profiler
2+
use once_cell::sync::Lazy;
23
use std::{
34
cell::RefCell,
45
collections::{BTreeMap, HashSet},
@@ -10,7 +11,7 @@ use std::{
1011
time::{Duration, Instant},
1112
};
1213

13-
use once_cell::sync::Lazy;
14+
use crate::tree::{Idx, Tree};
1415

1516
/// Filtering syntax
1617
/// env RA_PROFILE=* // dump everything
@@ -138,20 +139,20 @@ impl Filter {
138139

139140
struct ProfileStack {
140141
starts: Vec<Instant>,
141-
messages: Vec<Message>,
142142
filter: Filter,
143+
messages: Tree<Message>,
143144
}
144145

146+
#[derive(Default)]
145147
struct Message {
146-
level: usize,
147148
duration: Duration,
148149
label: Label,
149150
detail: Option<String>,
150151
}
151152

152153
impl ProfileStack {
153154
fn new() -> ProfileStack {
154-
ProfileStack { starts: Vec::new(), messages: Vec::new(), filter: Default::default() }
155+
ProfileStack { starts: Vec::new(), messages: Tree::default(), filter: Default::default() }
155156
}
156157

157158
fn push(&mut self, label: Label) -> bool {
@@ -171,73 +172,64 @@ impl ProfileStack {
171172
}
172173

173174
self.starts.push(Instant::now());
175+
self.messages.start();
174176
true
175177
}
176178

177179
pub fn pop(&mut self, label: Label, detail: Option<String>) {
178180
let start = self.starts.pop().unwrap();
179181
let duration = start.elapsed();
180182
let level = self.starts.len();
181-
self.messages.push(Message { level, duration, label, detail });
183+
self.messages.finish(Message { duration, label, detail });
182184
if level == 0 {
183185
let longer_than = self.filter.longer_than;
184186
// Convert to millis for comparison to avoid problems with rounding
185187
// (otherwise we could print `0ms` despite user's `>0` filter when
186188
// `duration` is just a few nanos).
187189
if duration.as_millis() > longer_than.as_millis() {
188190
let stderr = stderr();
189-
print(&self.messages, longer_than, &mut stderr.lock());
191+
if let Some(root) = self.messages.root() {
192+
print(&self.messages, root, 0, longer_than, &mut stderr.lock());
193+
}
190194
}
191195
self.messages.clear();
192196
assert!(self.starts.is_empty())
193197
}
194198
}
195199
}
196200

197-
fn print(msgs: &[Message], longer_than: Duration, out: &mut impl Write) {
198-
if msgs.is_empty() {
199-
return;
200-
}
201-
let children_map = idx_to_children(msgs);
202-
let root_idx = msgs.len() - 1;
203-
print_for_idx(root_idx, &children_map, msgs, longer_than, out);
204-
}
205-
206-
fn print_for_idx(
207-
current_idx: usize,
208-
children_map: &[Vec<usize>],
209-
msgs: &[Message],
201+
fn print(
202+
tree: &Tree<Message>,
203+
curr: Idx<Message>,
204+
level: u32,
210205
longer_than: Duration,
211206
out: &mut impl Write,
212207
) {
213-
let current = &msgs[current_idx];
214-
let current_indent = " ".repeat(current.level);
215-
let detail = current.detail.as_ref().map(|it| format!(" @ {}", it)).unwrap_or_default();
208+
let current_indent = " ".repeat(level as usize);
209+
let detail = tree[curr].detail.as_ref().map(|it| format!(" @ {}", it)).unwrap_or_default();
216210
writeln!(
217211
out,
218212
"{}{:5}ms - {}{}",
219213
current_indent,
220-
current.duration.as_millis(),
221-
current.label,
214+
tree[curr].duration.as_millis(),
215+
tree[curr].label,
222216
detail,
223217
)
224218
.expect("printing profiling info");
225219

226-
let longer_than_millis = longer_than.as_millis();
227-
let children_indices = &children_map[current_idx];
228220
let mut accounted_for = Duration::default();
229221
let mut short_children = BTreeMap::new(); // Use `BTreeMap` to get deterministic output.
222+
for child in tree.children(curr) {
223+
accounted_for += tree[child].duration;
230224

231-
for child_idx in children_indices.iter() {
232-
let child = &msgs[*child_idx];
233-
if child.duration.as_millis() > longer_than_millis {
234-
print_for_idx(*child_idx, children_map, msgs, longer_than, out);
225+
if tree[child].duration.as_millis() > longer_than.as_millis() {
226+
print(tree, child, level + 1, longer_than, out)
235227
} else {
236-
let pair = short_children.entry(child.label).or_insert((Duration::default(), 0));
237-
pair.0 += child.duration;
238-
pair.1 += 1;
228+
let (total_duration, cnt) =
229+
short_children.entry(tree[child].label).or_insert((Duration::default(), 0));
230+
*total_duration += tree[child].duration;
231+
*cnt += 1;
239232
}
240-
accounted_for += child.duration;
241233
}
242234

243235
for (child_msg, (duration, count)) in short_children.iter() {
@@ -246,122 +238,9 @@ fn print_for_idx(
246238
.expect("printing profiling info");
247239
}
248240

249-
let unaccounted_millis = (current.duration - accounted_for).as_millis();
250-
if !children_indices.is_empty()
251-
&& unaccounted_millis > 0
252-
&& unaccounted_millis > longer_than_millis
253-
{
254-
writeln!(out, " {}{:5}ms - ???", current_indent, unaccounted_millis)
241+
let unaccounted = tree[curr].duration - accounted_for;
242+
if tree.children(curr).next().is_some() && unaccounted > longer_than {
243+
writeln!(out, " {}{:5}ms - ???", current_indent, unaccounted.as_millis())
255244
.expect("printing profiling info");
256245
}
257246
}
258-
259-
/// Returns a mapping from an index in the `msgs` to the vector with the indices of its children.
260-
///
261-
/// This assumes that the entries in `msgs` are in the order of when the calls to `profile` finish.
262-
/// In other words, a postorder of the call graph. In particular, the root is the last element of
263-
/// `msgs`.
264-
fn idx_to_children(msgs: &[Message]) -> Vec<Vec<usize>> {
265-
// Initialize with the index of the root; `msgs` and `ancestors` should be never empty.
266-
assert!(!msgs.is_empty());
267-
let mut ancestors = vec![msgs.len() - 1];
268-
let mut result: Vec<Vec<usize>> = vec![vec![]; msgs.len()];
269-
for (idx, msg) in msgs[..msgs.len() - 1].iter().enumerate().rev() {
270-
// We need to find the parent of the current message, i.e., the last ancestor that has a
271-
// level lower than the current message.
272-
while msgs[*ancestors.last().unwrap()].level >= msg.level {
273-
ancestors.pop();
274-
}
275-
result[*ancestors.last().unwrap()].push(idx);
276-
ancestors.push(idx);
277-
}
278-
// Note that above we visited all children from the last to the first one. Let's reverse vectors
279-
// to get the more natural order where the first element is the first child.
280-
for vec in result.iter_mut() {
281-
vec.reverse();
282-
}
283-
result
284-
}
285-
286-
#[cfg(test)]
287-
mod tests {
288-
use super::*;
289-
290-
#[test]
291-
fn test_basic_profile() {
292-
let s = vec!["profile1".to_string(), "profile2".to_string()];
293-
let f = Filter::new(2, s, Duration::new(0, 0));
294-
set_filter(f);
295-
profiling_function1();
296-
}
297-
298-
fn profiling_function1() {
299-
let _p = profile("profile1");
300-
profiling_function2();
301-
}
302-
303-
fn profiling_function2() {
304-
let _p = profile("profile2");
305-
}
306-
307-
#[test]
308-
fn test_longer_than() {
309-
let mut result = vec![];
310-
let msgs = vec![
311-
Message { level: 1, duration: Duration::from_nanos(3), label: "bar", detail: None },
312-
Message { level: 1, duration: Duration::from_nanos(2), label: "bar", detail: None },
313-
Message { level: 0, duration: Duration::from_millis(1), label: "foo", detail: None },
314-
];
315-
print(&msgs, Duration::from_millis(0), &mut result);
316-
// The calls to `bar` are so short that they'll be rounded to 0ms and should get collapsed
317-
// when printing.
318-
assert_eq!(
319-
std::str::from_utf8(&result).unwrap(),
320-
" 1ms - foo\n 0ms - bar (2 calls)\n"
321-
);
322-
}
323-
324-
#[test]
325-
fn test_unaccounted_for_topmost() {
326-
let mut result = vec![];
327-
let msgs = vec![
328-
Message { level: 1, duration: Duration::from_millis(2), label: "bar", detail: None },
329-
Message { level: 0, duration: Duration::from_millis(5), label: "foo", detail: None },
330-
];
331-
print(&msgs, Duration::from_millis(0), &mut result);
332-
assert_eq!(
333-
std::str::from_utf8(&result).unwrap().lines().collect::<Vec<_>>(),
334-
vec![
335-
" 5ms - foo",
336-
" 2ms - bar",
337-
" 3ms - ???",
338-
// Dummy comment to improve formatting
339-
]
340-
);
341-
}
342-
343-
#[test]
344-
fn test_unaccounted_for_multiple_levels() {
345-
let mut result = vec![];
346-
let msgs = vec![
347-
Message { level: 2, duration: Duration::from_millis(3), label: "baz", detail: None },
348-
Message { level: 1, duration: Duration::from_millis(5), label: "bar", detail: None },
349-
Message { level: 2, duration: Duration::from_millis(2), label: "baz", detail: None },
350-
Message { level: 1, duration: Duration::from_millis(4), label: "bar", detail: None },
351-
Message { level: 0, duration: Duration::from_millis(9), label: "foo", detail: None },
352-
];
353-
print(&msgs, Duration::from_millis(0), &mut result);
354-
assert_eq!(
355-
std::str::from_utf8(&result).unwrap().lines().collect::<Vec<_>>(),
356-
vec![
357-
" 9ms - foo",
358-
" 5ms - bar",
359-
" 3ms - baz",
360-
" 2ms - ???",
361-
" 4ms - bar",
362-
" 2ms - baz",
363-
" 2ms - ???",
364-
]
365-
);
366-
}
367-
}

crates/ra_prof/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod memory_usage;
44
#[cfg(feature = "cpu_profiler")]
55
mod google_cpu_profiler;
66
mod hprof;
7+
mod tree;
78

89
use std::cell::RefCell;
910

crates/ra_prof/src/tree.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::ops;
2+
3+
use ra_arena::Arena;
4+
5+
#[derive(Default)]
6+
pub struct Tree<T> {
7+
nodes: Arena<Node<T>>,
8+
current_path: Vec<(Idx<T>, Option<Idx<T>>)>,
9+
}
10+
11+
pub type Idx<T> = ra_arena::Idx<Node<T>>;
12+
13+
impl<T> Tree<T> {
14+
pub fn start(&mut self)
15+
where
16+
T: Default,
17+
{
18+
let me = self.nodes.alloc(Node::new(T::default()));
19+
if let Some((parent, last_child)) = self.current_path.last_mut() {
20+
let slot = match *last_child {
21+
Some(last_child) => &mut self.nodes[last_child].next_sibling,
22+
None => &mut self.nodes[*parent].first_child,
23+
};
24+
let prev = slot.replace(me);
25+
assert!(prev.is_none());
26+
*last_child = Some(me);
27+
}
28+
29+
self.current_path.push((me, None));
30+
}
31+
32+
pub fn finish(&mut self, data: T) {
33+
let (me, _last_child) = self.current_path.pop().unwrap();
34+
self.nodes[me].data = data;
35+
}
36+
37+
pub fn root(&self) -> Option<Idx<T>> {
38+
self.nodes.iter().next().map(|(idx, _)| idx)
39+
}
40+
41+
pub fn children(&self, idx: Idx<T>) -> impl Iterator<Item = Idx<T>> + '_ {
42+
NodeIter { nodes: &self.nodes, next: self.nodes[idx].first_child }
43+
}
44+
pub fn clear(&mut self) {
45+
self.nodes.clear();
46+
self.current_path.clear();
47+
}
48+
}
49+
50+
impl<T> ops::Index<Idx<T>> for Tree<T> {
51+
type Output = T;
52+
fn index(&self, index: Idx<T>) -> &T {
53+
&self.nodes[index].data
54+
}
55+
}
56+
57+
pub struct Node<T> {
58+
data: T,
59+
first_child: Option<Idx<T>>,
60+
next_sibling: Option<Idx<T>>,
61+
}
62+
63+
impl<T> Node<T> {
64+
fn new(data: T) -> Node<T> {
65+
Node { data, first_child: None, next_sibling: None }
66+
}
67+
}
68+
69+
struct NodeIter<'a, T> {
70+
nodes: &'a Arena<Node<T>>,
71+
next: Option<Idx<T>>,
72+
}
73+
74+
impl<'a, T> Iterator for NodeIter<'a, T> {
75+
type Item = Idx<T>;
76+
77+
fn next(&mut self) -> Option<Idx<T>> {
78+
self.next.map(|next| {
79+
self.next = self.nodes[next].next_sibling;
80+
next
81+
})
82+
}
83+
}

0 commit comments

Comments
 (0)