Skip to content

Commit

Permalink
Directly write to output instead of local String buffer
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Mark-Simulacrum committed Mar 24, 2023
1 parent 69fb82b commit 206a6ac
Showing 1 changed file with 22 additions and 21 deletions.
43 changes: 22 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,19 +146,24 @@ pub fn demangle(mut s: &str) -> Demangle {
}

#[cfg(feature = "std")]
fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
let mut line = std::borrow::Cow::Borrowed(line);
fn demangle_line(
line: &str,
output: &mut impl std::io::Write,
include_hash: bool,
) -> std::io::Result<()> {
let mut head = 0;
loop {
while head < line.len() {
// Move to the next potential match
head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
let next_head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
(Some(idx), None) | (None, Some(idx)) => head + idx,
(Some(idx1), Some(idx2)) => head + idx1.min(idx2),
(None, None) => {
// No more matches, we can return our line.
return line;
// No more matches...
line.len()
}
};
output.write_all(line[head..next_head].as_bytes())?;
head = next_head;
// Find the non-matching character.
//
// If we do not find a character, then until the end of the line is the
Expand All @@ -169,29 +174,26 @@ fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
.unwrap_or(line.len());

let mangled = &line[head..match_end];
head = head + mangled.len();
if let Ok(demangled) = try_demangle(mangled) {
let demangled = if include_hash {
format!("{}", demangled)
if include_hash {
write!(output, "{}", demangled)?;
} else {
format!("{:#}", demangled)
};
line.to_mut().replace_range(head..match_end, &demangled);
// Start again after the replacement.
head = head + demangled.len();
write!(output, "{:#}", demangled)?;
}
} else {
// Skip over the full symbol. We don't try to find a partial Rust symbol in the wider
// matched text today.
head = head + mangled.len();
output.write_all(mangled.as_bytes())?;
}
}
Ok(())
}

/// Process a stream of data from `input` into the provided `output`, demangling any symbols found
/// within.
///
/// This currently is implemented by buffering each line of input in memory, but that may be
/// changed in the future. Symbols never cross line boundaries so this is just an implementation
/// detail.
/// Note that the underlying implementation will perform many relatively small writes to the
/// output. If the output is expensive to write to (e.g., requires syscalls), consider using
/// `std::io::BufWriter`.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
Expand All @@ -206,8 +208,7 @@ pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
// trailing data during demangling. In the future we might directly stream to the output but at
// least right now that seems to be less efficient.
while input.read_line(&mut buf)? > 0 {
let demangled_line = demangle_line(&buf, include_hash);
output.write_all(demangled_line.as_bytes())?;
demangle_line(&buf, output, include_hash)?;
buf.clear();
}
Ok(())
Expand Down

0 comments on commit 206a6ac

Please sign in to comment.