comparison rust/hg-core/src/dirstate_tree/status.rs @ 48745:94e36b230990

status: prefer relative paths in Rust code … when the repository root is under the current directory, so the kernel needs to traverse fewer directory in every call to `read_dir` or `symlink_metadata`. Better yet would be to use libc functions like `openat` and `fstatat` to remove such repeated traversals entirely, but the standard library does not provide APIs based on those. Maybe with a crate like https://crates.io/crates/openat instead? Benchmarks of `rhg status` show that this patch is neutral in some configurations, and makes the command up to ~20% faster in others. Below is semi-arbitrary subset of results. The four numeric columns are: time (in seconds) with this changeset’s parent, time with this changeset, time difference (negative is better), time ratio (less than 1 is better). ``` mercurial-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.0061 -> 0.0059: -0.0002 (0.97) mercurial-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0029 -> 0.0028: -0.0001 (0.97) mozilla-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.2110 -> 0.2102: -0.0007 (1.00) mozilla-dirstate-v2 | default-copies-clean.ignored.pbr | 0.0489 -> 0.0401: -0.0088 (0.82) mozilla-dirstate-v2 | default-copies-clean.no-iu.pbr | 0.0479 -> 0.0393: -0.0085 (0.82) mozilla-dirstate-v2 | default-copies-large.all.pbr | 0.1262 -> 0.1210: -0.0051 (0.96) mozilla-dirstate-v2 | default-copies-small.ignored-unknown.pbr | 0.1262 -> 0.1200: -0.0062 (0.95) mozilla-dirstate-v2 | default-copies-small.ignored.pbr | 0.0536 -> 0.0417: -0.0119 (0.78) mozilla-dirstate-v2 | default-copies-small.no-iu.pbr | 0.0482 -> 0.0393: -0.0089 (0.81) mozilla-dirstate-v2 | default-plain-clean.ignored.pbr | 0.0518 -> 0.0402: -0.0116 (0.78) mozilla-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0481 -> 0.0392: -0.0088 (0.82) mozilla-dirstate-v2 | default-plain-large.all.pbr | 0.1271 -> 0.1218: -0.0052 (0.96) mozilla-dirstate-v2 | default-plain-small.ignored-unknown.pbr | 0.1225 -> 0.1202: -0.0022 (0.98) mozilla-dirstate-v2 | default-plain-small.ignored.pbr | 0.0510 -> 0.0418: -0.0092 (0.82) mozilla-dirstate-v2 | default-plain-small.no-iu.pbr | 0.0480 -> 0.0394: -0.0086 (0.82) netbeans-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.1442 -> 0.1422: -0.0020 (0.99) netbeans-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0325 -> 0.0282: -0.0043 (0.87) ``` Differential Revision: https://phab.mercurial-scm.org/D12175
author Simon Sapin <simon.sapin@octobus.net>
date Fri, 21 Jan 2022 17:54:03 +0100
parents 4afb9627dc77
children 834c938227c6
comparison
equal deleted inserted replaced
48744:6e930bc45aeb 48745:94e36b230990
63 (Box::new(|&_| true), vec![], None) 63 (Box::new(|&_| true), vec![], None)
64 }; 64 };
65 65
66 let filesystem_time_at_status_start = 66 let filesystem_time_at_status_start =
67 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from); 67 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
68
69 // If the repository is under the current directory, prefer using a
70 // relative path, so the kernel needs to traverse fewer directory in every
71 // call to `read_dir` or `symlink_metadata`.
72 // This is effective in the common case where the current directory is the
73 // repository root.
74
75 // TODO: Better yet would be to use libc functions like `openat` and
76 // `fstatat` to remove such repeated traversals entirely, but the standard
77 // library does not provide APIs based on those.
78 // Maybe with a crate like https://crates.io/crates/openat instead?
79 let root_dir = if let Some(relative) = std::env::current_dir()
80 .ok()
81 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
82 {
83 relative
84 } else {
85 &root_dir
86 };
87
68 let outcome = DirstateStatus { 88 let outcome = DirstateStatus {
69 filesystem_time_at_status_start, 89 filesystem_time_at_status_start,
70 ..Default::default() 90 ..Default::default()
71 }; 91 };
72 let common = StatusCommon { 92 let common = StatusCommon {
750 /// 770 ///
751 /// * At the repository root, ignore that sub-directory 771 /// * At the repository root, ignore that sub-directory
752 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty 772 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
753 /// list instead. 773 /// list instead.
754 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> { 774 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
775 // `read_dir` returns a "not found" error for the empty path
776 let at_cwd = path == Path::new("");
777 let read_dir_path = if at_cwd { Path::new(".") } else { path };
755 let mut results = Vec::new(); 778 let mut results = Vec::new();
756 for entry in path.read_dir()? { 779 for entry in read_dir_path.read_dir()? {
757 let entry = entry?; 780 let entry = entry?;
758 let metadata = entry.metadata()?; 781 let metadata = entry.metadata()?;
759 let name = get_bytes_from_os_string(entry.file_name()); 782 let file_name = entry.file_name();
760 // FIXME don't do this when cached 783 // FIXME don't do this when cached
761 if name == b".hg" { 784 if file_name == ".hg" {
762 if is_at_repo_root { 785 if is_at_repo_root {
763 // Skip the repo’s own .hg (might be a symlink) 786 // Skip the repo’s own .hg (might be a symlink)
764 continue; 787 continue;
765 } else if metadata.is_dir() { 788 } else if metadata.is_dir() {
766 // A .hg sub-directory at another location means a subrepo, 789 // A .hg sub-directory at another location means a subrepo,
767 // skip it entirely. 790 // skip it entirely.
768 return Ok(Vec::new()); 791 return Ok(Vec::new());
769 } 792 }
770 } 793 }
794 let full_path = if at_cwd {
795 file_name.clone().into()
796 } else {
797 entry.path()
798 };
799 let base_name = get_bytes_from_os_string(file_name).into();
771 results.push(DirEntry { 800 results.push(DirEntry {
772 base_name: name.into(), 801 base_name,
773 full_path: entry.path(), 802 full_path,
774 metadata, 803 metadata,
775 }) 804 })
776 } 805 }
777 Ok(results) 806 Ok(results)
778 } 807 }