1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-31 13:07:46 +00:00

Merge pull request #2135 from tertsdiepraam/ls/flamegraph

`ls`: file name handling performance improvements
This commit is contained in:
Sylvestre Ledru 2021-04-26 20:47:13 +02:00 committed by GitHub
commit ae6145709e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 36 deletions

View file

@ -26,9 +26,32 @@ Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/n
`hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null" "ls -al -R tree > /dev/null"` `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null" "ls -al -R tree > /dev/null"`
(This assumes GNU ls is installed as `ls`) (This assumes GNU ls is installed as `ls`)
This can also be used to compare with version of ls built before your changes to ensure your change does not regress this This can also be used to compare with version of ls built before your changes to ensure your change does not regress this.
Here is a `bash` script for doing this comparison:
```bash
#!/bin/bash
cargo build --no-default-features --features ls --release
args="$@"
hyperfine "ls $args" "target/release/coreutils ls $args"
```
## Checking system call count ## Checking system call count
- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems. - Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems.
- Example: `strace -c target/release/coreutils ls -al -R tree` - Example: `strace -c target/release/coreutils ls -al -R tree`
## Cargo Flamegraph
With Cargo Flamegraph you can easily make a flamegraph of `ls`:
```bash
cargo flamegraph --cmd coreutils -- ls [additional parameters]
```
However, if the `-R` option is given, the output becomes pretty much useless due to recursion. We can fix this by merging all the direct recursive calls with `uniq`, below is a `bash` script that does this.
```bash
#!/bin/bash
cargo build --release --no-default-features --features ls
perf record target/release/coreutils ls "$@"
perf script | uniq | inferno-collapse-perf | inferno-flamegraph > flamegraph.svg
```

View file

@ -1056,7 +1056,9 @@ impl PathData {
) -> Self { ) -> Self {
let name = p_buf let name = p_buf
.file_name() .file_name()
.map_or(String::new(), |s| s.to_string_lossy().into_owned()); .unwrap_or_else(|| p_buf.iter().next_back().unwrap())
.to_string_lossy()
.into_owned();
let must_dereference = match &config.dereference { let must_dereference = match &config.dereference {
Dereference::All => true, Dereference::All => true,
Dereference::Args => command_line, Dereference::Args => command_line,
@ -1135,7 +1137,7 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
} }
} }
sort_entries(&mut files, &config); sort_entries(&mut files, &config);
display_items(&files, None, &config, &mut out); display_items(&files, &config, &mut out);
sort_entries(&mut dirs, &config); sort_entries(&mut dirs, &config);
for dir in dirs { for dir in dirs {
@ -1227,7 +1229,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>)
entries.append(&mut temp); entries.append(&mut temp);
display_items(&entries, Some(&dir.p_buf), config, out); display_items(&entries, config, out);
if config.recursive { if config.recursive {
for e in entries for e in entries
@ -1264,12 +1266,7 @@ fn pad_left(string: String, count: usize) -> String {
format!("{:>width$}", string, width = count) format!("{:>width$}", string, width = count)
} }
fn display_items( fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
items: &[PathData],
strip: Option<&Path>,
config: &Config,
out: &mut BufWriter<Stdout>,
) {
if config.format == Format::Long { if config.format == Format::Long {
let (mut max_links, mut max_size) = (1, 1); let (mut max_links, mut max_size) = (1, 1);
for item in items { for item in items {
@ -1278,12 +1275,10 @@ fn display_items(
max_size = size.max(max_size); max_size = size.max(max_size);
} }
for item in items { for item in items {
display_item_long(item, strip, max_links, max_size, config, out); display_item_long(item, max_links, max_size, config, out);
} }
} else { } else {
let names = items let names = items.iter().filter_map(|i| display_file_name(&i, config));
.iter()
.filter_map(|i| display_file_name(&i, strip, config));
match (&config.format, config.width) { match (&config.format, config.width) {
(Format::Columns, Some(width)) => { (Format::Columns, Some(width)) => {
@ -1355,7 +1350,6 @@ use uucore::fs::display_permissions;
fn display_item_long( fn display_item_long(
item: &PathData, item: &PathData,
strip: Option<&Path>,
max_links: usize, max_links: usize,
max_size: usize, max_size: usize,
config: &Config, config: &Config,
@ -1363,8 +1357,7 @@ fn display_item_long(
) { ) {
let md = match item.md() { let md = match item.md() {
None => { None => {
let filename = get_file_name(&item.p_buf, strip); show_error!("could not show file: {}", &item.p_buf.display());
show_error!("could not show file: {}", filename);
return; return;
} }
Some(md) => md, Some(md) => md,
@ -1407,7 +1400,7 @@ fn display_item_long(
// unwrap is fine because it fails when metadata is not available // unwrap is fine because it fails when metadata is not available
// but we already know that it is because it's checked at the // but we already know that it is because it's checked at the
// start of the function. // start of the function.
display_file_name(&item, strip, config).unwrap().contents, display_file_name(&item, config).unwrap().contents,
); );
} }
@ -1558,17 +1551,6 @@ fn display_file_type(file_type: FileType) -> char {
} }
} }
fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
let mut name = match strip {
Some(prefix) => name.strip_prefix(prefix).unwrap_or(name),
None => name,
};
if name.as_os_str().is_empty() {
name = Path::new(".");
}
name.to_string_lossy().into_owned()
}
#[cfg(unix)] #[cfg(unix)]
fn file_is_executable(md: &Metadata) -> bool { fn file_is_executable(md: &Metadata) -> bool {
// Mode always returns u32, but the flags might not be, based on the platform // Mode always returns u32, but the flags might not be, based on the platform
@ -1605,8 +1587,8 @@ fn classify_file(path: &PathData) -> Option<char> {
} }
} }
fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option<Cell> { fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style); let mut name = escape_name(&path.file_name, &config.quoting_style);
#[cfg(unix)] #[cfg(unix)]
{ {

View file

@ -171,7 +171,7 @@ impl Iterator for EscapedChar {
} }
} }
fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) -> (String, bool) { fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) -> (String, bool) {
let mut must_quote = false; let mut must_quote = false;
let mut escaped_str = String::with_capacity(name.len()); let mut escaped_str = String::with_capacity(name.len());
@ -201,7 +201,7 @@ fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool)
(escaped_str, must_quote) (escaped_str, must_quote)
} }
fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) { fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) {
// We need to keep track of whether we are in a dollar expression // We need to keep track of whether we are in a dollar expression
// because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n' // because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n'
let mut in_dollar = false; let mut in_dollar = false;
@ -249,7 +249,7 @@ fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) {
(escaped_str, must_quote) (escaped_str, must_quote)
} }
pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String { pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String {
match style { match style {
QuotingStyle::Literal { show_control } => { QuotingStyle::Literal { show_control } => {
if !show_control { if !show_control {
@ -257,7 +257,7 @@ pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String {
.flat_map(|c| EscapedChar::new_literal(c).hide_control()) .flat_map(|c| EscapedChar::new_literal(c).hide_control())
.collect() .collect()
} else { } else {
name name.into()
} }
} }
QuotingStyle::C { quotes } => { QuotingStyle::C { quotes } => {
@ -354,7 +354,7 @@ mod tests {
fn check_names(name: &str, map: Vec<(&str, &str)>) { fn check_names(name: &str, map: Vec<(&str, &str)>) {
assert_eq!( assert_eq!(
map.iter() map.iter()
.map(|(_, style)| escape_name(name.to_string(), &get_style(style))) .map(|(_, style)| escape_name(name, &get_style(style)))
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
map.iter() map.iter()
.map(|(correct, _)| correct.to_string()) .map(|(correct, _)| correct.to_string())