Skip to content

Commit

Permalink
Speed optimisations for recursive-heavy tasks
Browse files Browse the repository at this point in the history
This commit makes some changes based on analysing the stack trace with
`cargo flamegraph`. All changes are within the creation of the `Meta`
struct since this takes up most of the processing time. Some heavy
operations, namely memory allocations and system calls, have been either reduced or
deferred until the information is needed, significantly speeding up the
`-R` and `--tree` options.
  • Loading branch information
0jdxt committed Nov 12, 2020
1 parent b6bd1d3 commit 13d7321
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 188 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions src/display.rs
Expand Up @@ -15,19 +15,14 @@ const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──"
const BLANK: &str = " ";

pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
let term_width = match terminal_size() {
Some((w, _)) => Some(w.0 as usize),
None => None,
};

inner_display_grid(
&DisplayOption::None,
metas,
&flags,
colors,
icons,
0,
term_width,
terminal_size().map(|s| (s.0).0 as usize), // terminal width
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/icon.rs
Expand Up @@ -77,13 +77,13 @@ impl Icons {
"\u{f2dc}" // ""
} else if let Some(icon) = self
.icons_by_name
.get(name.file_name().to_lowercase().as_str())
.get(name.get_name().to_ascii_lowercase().as_str())
{
// Use the known names.
icon
} else if let Some(icon) = name.extension().and_then(|extension| {
self.icons_by_extension
.get(extension.to_lowercase().as_str())
.get(extension.to_ascii_lowercase().as_str())
}) {
// Use the known extensions.
icon
Expand Down
1 change: 0 additions & 1 deletion src/meta/date.rs
Expand Up @@ -7,7 +7,6 @@ use time::{Duration, Timespec};

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Date(time::Tm);

impl<'a> From<&'a Metadata> for Date {
fn from(meta: &'a Metadata) -> Self {
let modified_time = meta.modified().expect("failed to retrieve modified date");
Expand Down
32 changes: 11 additions & 21 deletions src/meta/mod.rs
Expand Up @@ -26,7 +26,6 @@ use crate::flags::{Display, Flags, Layout};
use crate::print_error;

use std::fs::read_link;
use std::io::{Error, ErrorKind};
use std::path::{Component, Path, PathBuf};

#[derive(Clone, Debug)]
Expand All @@ -50,11 +49,7 @@ impl Meta {
depth: usize,
flags: &Flags,
) -> Result<Option<Vec<Meta>>, std::io::Error> {
if depth == 0 {
return Ok(None);
}

if flags.display == Display::DirectoryItself {
if depth == 0 || flags.display == Display::DirectoryItself {
return Ok(None);
}

Expand All @@ -79,10 +74,8 @@ impl Meta {
let mut content: Vec<Meta> = Vec::new();

if let Display::All = flags.display {
let mut current_meta;

current_meta = self.clone();
current_meta.name.name = ".".to_owned();
let mut current_meta = self.clone();
current_meta.name.set_name(".".to_owned());

let parent_meta =
Self::from_path(&self.path.join(Component::ParentDir), flags.dereference.0)?;
Expand All @@ -92,11 +85,9 @@ impl Meta {
}

for entry in entries {
let path = entry?.path();
let entry = entry?;

let name = path
.file_name()
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "invalid file name"))?;
let name = entry.file_name();

if flags.ignore_globs.0.is_match(&name) {
continue;
Expand All @@ -108,18 +99,18 @@ impl Meta {
}
}

let mut entry_meta = match Self::from_path(&path, flags.dereference.0) {
let mut entry_meta = match Self::from_path(&entry.path(), flags.dereference.0) {
Ok(res) => res,
Err(err) => {
print_error!("lsd: {}: {}\n", path.display(), err);
print_error!("lsd: {:?}: {}\n", entry.file_name(), err);
continue;
}
};

match entry_meta.recurse_into(depth - 1, &flags) {
Ok(content) => entry_meta.content = content,
Err(err) => {
print_error!("lsd: {}: {}\n", path.display(), err);
print_error!("lsd: {:?}: {}\n", entry.file_name(), err);
continue;
}
};
Expand Down Expand Up @@ -192,10 +183,9 @@ impl Meta {

pub fn from_path(path: &Path, dereference: bool) -> Result<Self, std::io::Error> {
// If the file is a link then retrieve link metadata instead with target metadata (if present).
let (metadata, symlink_meta) = if read_link(path).is_ok() && !dereference {
(path.symlink_metadata()?, path.metadata().ok())
} else {
(path.metadata()?, None)
let (metadata, symlink_meta) = match path.symlink_metadata() {
Ok(metadata) if !dereference => (metadata, path.metadata().ok()),
_ => (path.metadata()?, None),
};

#[cfg(unix)]
Expand Down
154 changes: 64 additions & 90 deletions src/meta/name.rs
@@ -1,8 +1,8 @@
use crate::color::{ColoredString, Colors, Elem};
use crate::icon::Icons;
use crate::meta::filetype::FileType;
use std::borrow::Cow;
use std::cmp::{Ordering, PartialOrd};
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};

#[derive(Debug)]
Expand All @@ -14,86 +14,74 @@ pub enum DisplayOption<'a> {

#[derive(Clone, Debug, Eq)]
pub struct Name {
pub name: String,
name: Option<String>,
path: PathBuf,
extension: Option<String>,
file_type: FileType,
}

impl Name {
pub fn new(path: &Path, file_type: FileType) -> Self {
let name = match path.file_name() {
Some(name) => name.to_string_lossy().to_string(),
None => path.to_string_lossy().to_string(),
};

let extension = path
.extension()
.map(|ext| ext.to_string_lossy().to_string());

Self {
name,
path: PathBuf::from(path),
extension,
name: None,
path: path.into(),
file_type,
}
}

pub fn file_name(&self) -> &str {
self.path
.file_name()
.and_then(OsStr::to_str)
.unwrap_or(&self.name)
pub fn extension(&self) -> Option<Cow<'_, str>> {
self.path.extension().map(|e| e.to_string_lossy())
}

fn relative_path<T: AsRef<Path> + Clone>(&self, base_path: T) -> PathBuf {
let base_path = base_path.as_ref();
pub fn set_name(&mut self, new_name: String) {
self.name = Some(new_name);
}

if self.path == base_path {
return PathBuf::from(AsRef::<Path>::as_ref(&Component::CurDir));
pub fn get_name(&self) -> Cow<'_, str> {
if let Some(name) = &self.name {
Cow::from(name)
} else {
match self.path.file_name() {
Some(name) => name.to_string_lossy(),
None => self.path.to_string_lossy(),
}
}
}

let shared_components: PathBuf = self
.path
.components()
.zip(base_path.components())
.take_while(|(target_component, base_component)| target_component == base_component)
.map(|tuple| tuple.0)
.collect();

base_path
.strip_prefix(&shared_components)
.unwrap()
.components()
.map(|_| Component::ParentDir)
.chain(
self.path
.strip_prefix(&shared_components)
.unwrap()
.components(),
)
.collect()
pub fn file_type(&self) -> FileType {
self.file_type
}

pub fn escape(&self, string: &str) -> String {
if string
.chars()
.all(|c| c >= 0x20 as char && c != 0x7f as char)
{
string.to_string()
fn relative_path(&self, base_path: &Path) -> PathBuf {
if self.path == base_path {
PathBuf::from(AsRef::<Path>::as_ref(&Component::CurDir))
} else if let Some(prefix) = self.path.ancestors().find(|a| base_path.starts_with(a)) {
std::iter::repeat(Component::ParentDir)
.take(
base_path
.strip_prefix(&prefix)
.unwrap()
.components()
.count(),
)
.chain(self.path.strip_prefix(&prefix).unwrap().components())
.collect()
} else {
let mut chars = String::new();
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.
if c >= 0x20 as char && c != 0x7f as char {
chars.push(c);
} else {
chars += &c.escape_default().collect::<String>();
}
PathBuf::new()
}
}

pub fn escape(&self, string: &str) -> String {
let mut chars = String::with_capacity(string.len());
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.
if c >= 0x20 as char && c != 0x7f as char {
chars.push(c);
} else {
chars += &c.escape_default().collect::<String>();
}
chars
}
chars
}

pub fn render(
Expand All @@ -102,21 +90,12 @@ impl Name {
icons: &Icons,
display_option: &DisplayOption,
) -> ColoredString {
let content = match display_option {
DisplayOption::FileName => {
format!("{}{}", icons.get(self), self.escape(self.file_name()))
}
DisplayOption::Relative { base_path } => format!(
"{}{}",
icons.get(self),
self.escape(&self.relative_path(base_path).to_string_lossy())
),
DisplayOption::None => format!(
"{}{}",
icons.get(self),
self.escape(&self.path.to_string_lossy())
),
let name: String = if let DisplayOption::Relative { base_path } = display_option {
self.relative_path(base_path).to_string_lossy().into()
} else {
self.get_name().into()
};
let content = format!("{}{}", icons.get(self), self.escape(&name));

let elem = match self.file_type {
FileType::CharDevice => Elem::CharDevice,
Expand All @@ -131,33 +110,25 @@ impl Name {

colors.colorize_using_path(content, &self.path, &elem)
}

pub fn extension(&self) -> Option<&str> {
self.extension.as_deref()
}

pub fn file_type(&self) -> FileType {
self.file_type
}
}

impl Ord for Name {
fn cmp(&self, other: &Self) -> Ordering {
self.name.to_lowercase().cmp(&other.name.to_lowercase())
self.get_name()
.to_ascii_lowercase()
.cmp(&other.get_name().to_ascii_lowercase())
}
}

impl PartialOrd for Name {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.name
.to_lowercase()
.partial_cmp(&other.name.to_lowercase())
Some(self.cmp(other))
}
}

impl PartialEq for Name {
fn eq(&self, other: &Self) -> bool {
self.name.eq_ignore_ascii_case(&other.name.to_lowercase())
self.get_name().eq_ignore_ascii_case(&other.get_name())
}
}

Expand Down Expand Up @@ -335,7 +306,10 @@ mod test {
},
);

assert_eq!(Some("txt"), name.extension());
assert_eq!(
Some("txt".to_string()),
name.extension().map(|c| c.to_string())
);
}

#[test]
Expand Down Expand Up @@ -496,7 +470,7 @@ mod test {
);
let base_path = PathBuf::from("/home/parent1");

assert_eq!(PathBuf::from("child"), name.relative_path(base_path),)
assert_eq!(PathBuf::from("child"), name.relative_path(&base_path),)
}

#[test]
Expand All @@ -512,7 +486,7 @@ mod test {

assert_eq!(
PathBuf::from("../../grand-parent1/parent1/child"),
name.relative_path(base_path),
name.relative_path(&base_path),
)
}

Expand Down

0 comments on commit 13d7321

Please sign in to comment.