Skip to content

Commit 13be70d

Browse files
Directly write to output instead of local String buffer
Previous measurements showed this to be a net loss in performance, but further investigation shows that is likely to be a result of increased syscalls in the test benchmark. Adjusting the benchmark to buffer the output (e.g., with std::io::BufWriter) makes this a win, around 16% faster. Documentation on demangle_stream is updated to recommend buffering the output writer.
1 parent 69fb82b commit 13be70d

File tree

1 file changed

+31
-23
lines changed

1 file changed

+31
-23
lines changed

Diff for: src/lib.rs

+31-23
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,24 @@ pub fn demangle(mut s: &str) -> Demangle {
146146
}
147147

148148
#[cfg(feature = "std")]
149-
fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
150-
let mut line = std::borrow::Cow::Borrowed(line);
149+
fn demangle_line(
150+
line: &str,
151+
output: &mut impl std::io::Write,
152+
include_hash: bool,
153+
) -> std::io::Result<()> {
151154
let mut head = 0;
152-
loop {
155+
while head < line.len() {
153156
// Move to the next potential match
154-
head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
157+
let next_head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
155158
(Some(idx), None) | (None, Some(idx)) => head + idx,
156159
(Some(idx1), Some(idx2)) => head + idx1.min(idx2),
157160
(None, None) => {
158-
// No more matches, we can return our line.
159-
return line;
161+
// No more matches...
162+
line.len()
160163
}
161164
};
165+
output.write_all(line[head..next_head].as_bytes())?;
166+
head = next_head;
162167
// Find the non-matching character.
163168
//
164169
// If we do not find a character, then until the end of the line is the
@@ -169,29 +174,26 @@ fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
169174
.unwrap_or(line.len());
170175

171176
let mangled = &line[head..match_end];
177+
head = head + mangled.len();
172178
if let Ok(demangled) = try_demangle(mangled) {
173-
let demangled = if include_hash {
174-
format!("{}", demangled)
179+
if include_hash {
180+
write!(output, "{}", demangled)?;
175181
} else {
176-
format!("{:#}", demangled)
177-
};
178-
line.to_mut().replace_range(head..match_end, &demangled);
179-
// Start again after the replacement.
180-
head = head + demangled.len();
182+
write!(output, "{:#}", demangled)?;
183+
}
181184
} else {
182-
// Skip over the full symbol. We don't try to find a partial Rust symbol in the wider
183-
// matched text today.
184-
head = head + mangled.len();
185+
output.write_all(mangled.as_bytes())?;
185186
}
186187
}
188+
Ok(())
187189
}
188190

189191
/// Process a stream of data from `input` into the provided `output`, demangling any symbols found
190192
/// within.
191193
///
192-
/// This currently is implemented by buffering each line of input in memory, but that may be
193-
/// changed in the future. Symbols never cross line boundaries so this is just an implementation
194-
/// detail.
194+
/// Note that the underlying implementation will perform many relatively small writes to the
195+
/// output. If the output is expensive to write to (e.g., requires syscalls), consider using
196+
/// `std::io::BufWriter`.
195197
#[cfg(feature = "std")]
196198
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
197199
pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
@@ -206,8 +208,7 @@ pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
206208
// trailing data during demangling. In the future we might directly stream to the output but at
207209
// least right now that seems to be less efficient.
208210
while input.read_line(&mut buf)? > 0 {
209-
let demangled_line = demangle_line(&buf, include_hash);
210-
output.write_all(demangled_line.as_bytes())?;
211+
demangle_line(&buf, output, include_hash)?;
211212
buf.clear();
212213
}
213214
Ok(())
@@ -560,11 +561,18 @@ mod tests {
560561
);
561562
}
562563

564+
#[cfg(feature = "std")]
565+
fn demangle_str(input: &str) -> String {
566+
let mut output = Vec::new();
567+
super::demangle_line(input, &mut output, false);
568+
String::from_utf8(output).unwrap()
569+
}
570+
563571
#[test]
564572
#[cfg(feature = "std")]
565573
fn find_multiple() {
566574
assert_eq!(
567-
super::demangle_line("_ZN3fooE.llvm moocow _ZN3fooE.llvm", false),
575+
demangle_str("_ZN3fooE.llvm moocow _ZN3fooE.llvm"),
568576
"foo.llvm moocow foo.llvm"
569577
);
570578
}
@@ -573,7 +581,7 @@ mod tests {
573581
#[cfg(feature = "std")]
574582
fn interleaved_new_legacy() {
575583
assert_eq!(
576-
super::demangle_line("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm", false),
584+
demangle_str("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm"),
577585
"foo.llvm moocow <mio::sys::unix::selector::epoll::Selector>::select foo.llvm"
578586
);
579587
}

0 commit comments

Comments
 (0)