Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

12 changes: 7 additions & 5 deletions src/worker/gui_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,24 +447,26 @@ impl WorkspaceSession<'_> {
return Ok(HashMap::new());
}

// for each bookmark, BFS from its commit toward ancestors, stopping at log commits.
// those boundary log commits are the nearest visible ancestors (fork points).
// cost: O(depth_to_log) per bookmark instead of O(repo_size) per bookmark.
const MAX_BFS_VISITS: usize = 100_000;
let mut fork_map: HashMap<CommitId, Vec<String>> = HashMap::new();
for (commit_id, labels) in &commits_to_labels {
let mut total_visits: usize = 0;
'outer: for (commit_id, labels) in &commits_to_labels {
let mut queue = VecDeque::new();
queue.push_back(commit_id.clone());
let mut visited: HashSet<CommitId> = HashSet::new();
visited.insert(commit_id.clone());

while let Some(current) = queue.pop_front() {
total_visits += 1;
if total_visits > MAX_BFS_VISITS {
break 'outer;
}
let commit = self.get_commit(&current)?;
for parent_id in commit.parent_ids() {
if !visited.insert(parent_id.clone()) {
continue;
}
if log_contains(parent_id)? {
// nearest visible ancestor — record without walking further
let entry = fork_map.entry(parent_id.clone()).or_default();
for label in labels {
if !entry.contains(label) {
Expand Down
1 change: 1 addition & 0 deletions src/worker/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod perf;
mod queries;
mod session;

Expand Down
116 changes: 116 additions & 0 deletions src/worker/tests/perf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::path::PathBuf;
use std::sync::mpsc;
use std::time::{Duration, Instant};

use anyhow::Result;

use crate::worker::{WorkerSession, queries};

const TIMEOUT_SECS: u64 = 30;

struct RevsetBench {
name: &'static str,
revset: &'static str,
}

const REVSETS: &[RevsetBench] = &[
RevsetBench {
name: "all()",
revset: "all()",
},
RevsetBench {
name: "::@",
revset: "::@",
},
RevsetBench {
name: "default",
revset: r#"present(@) | ancestors(immutable_heads().., 2) | trunk()"#,
},
RevsetBench {
name: "mine",
revset: "trunk() | (trunk()..(bookmarks() & mine())) | parents(bookmarks() & mine()) | @",
},
];

/// Run with: BENCH_REPO=/path/to/repo cargo test query_bench -- --ignored --nocapture
#[tokio::test]
#[ignore]
async fn query_bench() -> Result<()> {
let repo_path = match std::env::var("BENCH_REPO") {
Ok(p) => PathBuf::from(p),
Err(_) => {
eprintln!("SKIP: set BENCH_REPO=/path/to/jj/repo to run this benchmark");
return Ok(());
}
};

if !repo_path.join(".jj").exists() {
eprintln!("SKIP: no .jj directory found at {}", repo_path.display());
return Ok(());
}

eprintln!("repo: {}", repo_path.display());

let mut session = WorkerSession::default();

let t0 = Instant::now();
let mut ws = session.load_workspace(&repo_path).await?;
eprintln!("load_workspace: {}ms", t0.elapsed().as_millis());

let t1 = Instant::now();
ws.import_and_snapshot(false, true).await?;
eprintln!("import_and_snapshot: {}ms", t1.elapsed().as_millis());

eprintln!();
eprintln!(
"{:<12} {:>10} {:>10} {:>10} {:>10} {:<6}",
"revset", "eval", "forks", "page", "total", "rows"
);
eprintln!("{}", "-".repeat(70));

for bench in REVSETS {
let (_cancel, rx) = mpsc::channel::<()>();
let bench_name = bench.name;

std::thread::spawn(move || {
if let Err(mpsc::RecvTimeoutError::Timeout) =
rx.recv_timeout(Duration::from_secs(TIMEOUT_SECS))
{
eprintln!("{bench_name:<12} TIMEOUT (>{TIMEOUT_SECS}s)");
std::process::exit(1);
}
});

let t_total = Instant::now();

let t_eval = Instant::now();
let revset_result = ws.evaluate_revset_str(bench.revset);
let eval_ms = t_eval.elapsed().as_millis();

let revset = match revset_result {
Ok(r) => r,
Err(e) => {
eprintln!("{:<12} ERROR: {e}", bench.name);
continue;
}
};

let t_forks = Instant::now();
let hidden_forks = ws.compute_hidden_forks(bench.revset).unwrap_or_default();
let forks_ms = t_forks.elapsed().as_millis();

let t_page = Instant::now();
let state = queries::QueryState::new(50);
let mut qs = queries::QuerySession::new(&ws, &*revset, state, hidden_forks);
let page = qs.get_page()?;
let page_ms = t_page.elapsed().as_millis();

let total_ms = t_total.elapsed().as_millis();
eprintln!(
"{:<12} {:>9}ms {:>9}ms {:>9}ms {:>9}ms {:<6}",
bench.name, eval_ms, forks_ms, page_ms, total_ms, page.rows.len()
);
}

Ok(())
}
Loading