working_copy: Add is_file_states_sorted to tree state proto

See #2651 and a935a4f70c for more
background.

This speeds up `jj log` in a large repo with watchman enabled by around
9%:

```
$ hyperfine --sort command --warmup 3 --runs 20 -L bin \
jj-before,jj-after "target/release/{bin} -R ~/chromiumjj/src log"
Benchmark 1: target/release/jj-before -R ~/chromiumjj/src log
  Time (mean ± σ):     788.3 ms ±   3.4 ms    [User: 618.6 ms, System: 168.8 ms]
  Range (min … max):   783.1 ms … 793.3 ms    20 runs

Benchmark 2: target/release/jj-after -R ~/chromiumjj/src log
  Time (mean ± σ):     713.4 ms ±   5.2 ms    [User: 536.1 ms, System: 176.2 ms]
  Range (min … max):   706.6 ms … 724.7 ms    20 runs

Relative speed comparison
        1.11 ±  0.01  target/release/jj-before -R ~/chromiumjj/src log
        1.00          target/release/jj-after -R ~/chromiumjj/src log
```
This commit is contained in:
mlcui 2024-05-31 11:49:17 +10:00 committed by mlcui
parent 404f31cbc1
commit 458580cee2
3 changed files with 19 additions and 9 deletions

View file

@ -149,13 +149,17 @@ impl FileStatesMap {
FileStatesMap { data: Vec::new() }
}
// TODO: skip sorting if the data is known to be sorted?
fn from_proto_unsorted(mut data: Vec<crate::protos::working_copy::FileStateEntry>) -> Self {
data.sort_unstable_by(|entry1, entry2| {
let path1 = RepoPath::from_internal_string(&entry1.path);
let path2 = RepoPath::from_internal_string(&entry2.path);
path1.cmp(path2)
});
fn from_proto(
mut data: Vec<crate::protos::working_copy::FileStateEntry>,
is_sorted: bool,
) -> Self {
if !is_sorted {
data.sort_unstable_by(|entry1, entry2| {
let path1 = RepoPath::from_internal_string(&entry1.path);
let path2 = RepoPath::from_internal_string(&entry2.path);
path1.cmp(path2)
});
}
debug_assert!(is_file_state_entries_proto_unique_and_sorted(&data));
FileStatesMap { data }
}
@ -610,7 +614,8 @@ impl TreeState {
.collect();
self.tree_id = MergedTreeId::Merge(tree_ids_builder.build());
}
self.file_states = FileStatesMap::from_proto_unsorted(proto.file_states);
self.file_states =
FileStatesMap::from_proto(proto.file_states, proto.is_file_states_sorted);
self.sparse_patterns = sparse_patterns_from_proto(proto.sparse_patterns.as_ref());
self.watchman_clock = proto.watchman_clock;
Ok(())
@ -630,6 +635,8 @@ impl TreeState {
}
proto.file_states = self.file_states.data.clone();
// `FileStatesMap` is guaranteed to be sorted.
proto.is_file_states_sorted = true;
let mut sparse_patterns = crate::protos::working_copy::SparsePatterns::default();
for path in &self.sparse_patterns {
sparse_patterns
@ -1899,7 +1906,7 @@ mod tests {
new_proto_entry("b/e", 3),
new_proto_entry("bc", 5),
];
let mut file_states = FileStatesMap::from_proto_unsorted(data);
let mut file_states = FileStatesMap::from_proto(data, false);
let changed_file_states = vec![
new_owned_entry("aa", 10), // change

View file

@ -47,6 +47,7 @@ message TreeState {
// single (positive) value
repeated bytes tree_ids = 5;
repeated FileStateEntry file_states = 2;
bool is_file_states_sorted = 6;
SparsePatterns sparse_patterns = 3;
WatchmanClock watchman_clock = 4;
}

View file

@ -38,6 +38,8 @@ pub struct TreeState {
pub tree_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(message, repeated, tag = "2")]
pub file_states: ::prost::alloc::vec::Vec<FileStateEntry>,
#[prost(bool, tag = "6")]
pub is_file_states_sorted: bool,
#[prost(message, optional, tag = "3")]
pub sparse_patterns: ::core::option::Option<SparsePatterns>,
#[prost(message, optional, tag = "4")]