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 Oct 28, 2020
1 parent 3537575 commit e5eac65
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 172 deletions.
7 changes: 5 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -35,6 +35,7 @@ wild = "2.0.*"
globset = "0.4.*"
xdg = "2.1.*"
yaml-rust = "0.4.*"
chrono = "0.4.19"

[target.'cfg(unix)'.dependencies]
users = "0.11.*"
Expand Down
35 changes: 12 additions & 23 deletions src/meta/date.rs
@@ -1,50 +1,39 @@
use crate::color::{ColoredString, Colors, Elem};
use crate::flags::{DateFlag, Flags};
use chrono::{DateTime, Duration, Utc};
use chrono_humanize::HumanTime;
use std::fs::Metadata;
use std::time::UNIX_EPOCH;
use time::{Duration, Timespec};

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

impl<'a> From<&'a Metadata> for Date {
fn from(meta: &'a Metadata) -> Self {
let modified_time = meta.modified().expect("failed to retrieve modified date");

let modified_time_since_epoch =
modified_time.duration_since(UNIX_EPOCH).unwrap_or_default();

let time = time::at(Timespec::new(
modified_time_since_epoch.as_secs() as i64,
modified_time_since_epoch.subsec_nanos() as i32,
));

Date(time)
Date(modified_time.into())
}
}

impl Date {
pub fn render(&self, colors: &Colors, flags: &Flags) -> ColoredString {
let now = time::now();
let now = Utc::now();

let elem;
if self.0 > now - Duration::hours(1) {
elem = &Elem::HourOld;
let elem = if self.0 > now - Duration::hours(1) {
&Elem::HourOld
} else if self.0 > now - Duration::days(1) {
elem = &Elem::DayOld;
&Elem::DayOld
} else {
elem = &Elem::Older;
}
&Elem::Older
};

colors.colorize(self.date_string(&flags), elem)
}

pub fn date_string(&self, flags: &Flags) -> String {
match &flags.date {
DateFlag::Date => self.0.ctime().to_string(),
DateFlag::Relative => format!("{}", HumanTime::from(self.0 - time::now())),
DateFlag::Formatted(format) => self.0.to_local().strftime(&format).unwrap().to_string(),
DateFlag::Date => self.0.format("%a %b %e %X %Y").to_string(),
DateFlag::Relative => HumanTime::from(self.0 - Utc::now()).to_string(),
DateFlag::Formatted(format) => self.0.format(&format).to_string(),
}
}
}
Expand Down
26 changes: 13 additions & 13 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 Down Expand Up @@ -92,11 +91,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 +105,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 +189,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 All @@ -213,7 +209,11 @@ impl Meta {
Ok(Self {
inode,
path: path.to_path_buf(),
symlink: SymLink::from(path),
symlink: if symlink_meta.is_some() {
SymLink::from(path)
} else {
SymLink::default()
},
size: Size::from(&metadata),
date: Date::from(&metadata),
indicator: Indicator::from(file_type),
Expand Down
116 changes: 46 additions & 70 deletions src/meta/name.rs
Expand Up @@ -2,7 +2,6 @@ use crate::color::{ColoredString, Colors, Elem};
use crate::icon::Icons;
use crate::meta::filetype::FileType;
use std::cmp::{Ordering, PartialOrd};
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};

#[derive(Debug)]
Expand All @@ -22,14 +21,18 @@ pub struct Name {

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 full = path.to_string_lossy().to_string();

let name = if let Some(idx) = full.rfind('/') {
full.split_at(idx + 1).1.into()
} else {
full
};

let extension = path
.extension()
.map(|ext| ext.to_string_lossy().to_string());
let extension = match name.rfind('.') {
Some(idx) if idx > 0 => Some(name.split_at(idx + 1).1.into()),
_ => None,
};

Self {
name,
Expand All @@ -40,60 +43,40 @@ impl Name {
}

pub fn file_name(&self) -> &str {
self.path
.file_name()
.and_then(OsStr::to_str)
.unwrap_or(&self.name)
&self.name
}

fn relative_path<T: AsRef<Path> + Clone>(&self, base_path: T) -> PathBuf {
let base_path = base_path.as_ref();

fn relative_path(&self, base_path: &Path) -> PathBuf {
if self.path == base_path {
return PathBuf::from(AsRef::<Path>::as_ref(&Component::CurDir));
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 {
PathBuf::new()
}

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 escape(&self, string: &str) -> String {
if string
.chars()
.all(|c| c >= 0x20 as char && c != 0x7f as char)
{
string.to_string()
} 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>();
}
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 +85,14 @@ impl Name {
icons: &Icons,
display_option: &DisplayOption,
) -> ColoredString {
let content = match display_option {
DisplayOption::FileName => {
format!("{}{}", icons.get(self), self.escape(self.file_name()))
let name: String = {
if let DisplayOption::Relative { base_path } = display_option {
self.relative_path(base_path).to_string_lossy().into()
} else {
self.file_name().into()
}
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 content = format!("{}{}", icons.get(self), self.escape(&name));

let elem = match self.file_type {
FileType::CharDevice => Elem::CharDevice,
Expand All @@ -143,15 +119,15 @@ impl Name {

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

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

Expand Down Expand Up @@ -496,7 +472,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 +488,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 e5eac65

Please sign in to comment.