|
|
… |
290 unchanged lines |
| 291 |
291 |
|
/// A kept state with more built graphs than this sheds them. |
| 292 |
292 |
|
const KEPT_GRAPHS: usize = 2000; |
| 293 |
293 |
|
|
|
294 |
+ |
/// Text files of a merge rendered this many at a time (oxygen#25): a |
|
295 |
+ |
/// batch's graphs are built beside the state and dropped once rendered, |
|
296 |
+ |
/// so what is held for the whole tree is the renderings, not the graphs |
|
297 |
+ |
/// with every deleted line of their history. |
|
298 |
+ |
const MERGE_BATCH: usize = 500; |
|
299 |
+ |
|
|
300 |
+ |
thread_local! { |
|
301 |
+ |
/// A smaller batch for this thread's imports: tests set 1, so a small |
|
302 |
+ |
/// merge takes the batched path. |
|
303 |
+ |
static MERGE_BATCH_OVERRIDE: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) }; |
|
304 |
+ |
} |
|
305 |
+ |
|
|
306 |
+ |
#[cfg(test)] |
|
307 |
+ |
pub(crate) fn set_merge_batch_for_tests(n: Option<usize>) { |
|
308 |
+ |
MERGE_BATCH_OVERRIDE.with(|c| c.set(n)); |
|
309 |
+ |
} |
|
310 |
+ |
|
|
311 |
+ |
fn merge_batch() -> usize { |
|
312 |
+ |
MERGE_BATCH_OVERRIDE |
|
313 |
+ |
.with(|c| c.get()) |
|
314 |
+ |
.filter(|n| *n > 0) |
|
315 |
+ |
.unwrap_or(MERGE_BATCH) |
|
316 |
+ |
} |
|
317 |
+ |
|
|
318 |
+ |
impl Repo { |
|
319 |
+ |
/// As `State::materialize_except`, for a tree whose graphs would not |
|
320 |
+ |
/// fit in memory at once. The few files the change builder reads |
|
321 |
+ |
/// graphs for (conflicted ones, and renderings whose lines are not |
|
322 |
+ |
/// one per live atom) are built in the state afterwards. |
|
323 |
+ |
fn materialize_batched( |
|
324 |
+ |
&self, |
|
325 |
+ |
base: &mut State, |
|
326 |
+ |
need: &[crate::ids::NodeId], |
|
327 |
+ |
skip: &HashSet<crate::ids::NodeId>, |
|
328 |
+ |
) -> Result<crate::state::Snapshot> { |
|
329 |
+ |
use crate::graph::Materialized; |
|
330 |
+ |
use crate::state::{FileContent, FileState}; |
|
331 |
+ |
use rayon::prelude::*; |
|
332 |
+ |
let view = base.tree().view(); |
|
333 |
+ |
let mut rendered: HashMap<crate::ids::NodeId, (Vec<u8>, Materialized)> = |
|
334 |
+ |
HashMap::with_capacity(need.len()); |
|
335 |
+ |
let mut keep_built: Vec<crate::ids::NodeId> = Vec::new(); |
|
336 |
+ |
for batch in need.chunks(merge_batch()) { |
|
337 |
+ |
let t = std::time::Instant::now(); |
|
338 |
+ |
let graphs = self.build_graphs_detached(base, batch)?; |
|
339 |
+ |
crate::timing::add("render: build graphs, batch", t.elapsed()); |
|
340 |
+ |
let t = std::time::Instant::now(); |
|
341 |
+ |
let batch_rendered: Vec<(crate::ids::NodeId, Vec<u8>, Materialized)> = graphs |
|
342 |
+ |
.par_iter() |
|
343 |
+ |
.map(|(node, g)| { |
|
344 |
+ |
let (bytes, m) = base.render_graph(g); |
|
345 |
+ |
(*node, bytes, m) |
|
346 |
+ |
}) |
|
347 |
+ |
.collect(); |
|
348 |
+ |
drop(graphs); |
|
349 |
+ |
crate::timing::add("render: render, batch", t.elapsed()); |
|
350 |
+ |
for (node, bytes, m) in batch_rendered { |
|
351 |
+ |
if m.is_conflicted() |
|
352 |
+ |
|| m.live_atoms().len() != crate::diff::split_lines(&bytes).len() |
|
353 |
+ |
{ |
|
354 |
+ |
keep_built.push(node); |
|
355 |
+ |
} |
|
356 |
+ |
rendered.insert(node, (bytes, m)); |
|
357 |
+ |
} |
|
358 |
+ |
} |
|
359 |
+ |
if !keep_built.is_empty() { |
|
360 |
+ |
self.load_graphs_of(base, &keep_built)?; |
|
361 |
+ |
} |
|
362 |
+ |
let mut files = BTreeMap::new(); |
|
363 |
+ |
let mut unrendered = BTreeSet::new(); |
|
364 |
+ |
for (path, e) in view.entries.iter() { |
|
365 |
+ |
let f = if e.kind == NodeKind::File && !e.mode.opaque { |
|
366 |
+ |
match rendered.remove(&e.node) { |
|
367 |
+ |
Some((bytes, materialized)) => FileState { |
|
368 |
+ |
node: e.node, |
|
369 |
+ |
kind: e.kind, |
|
370 |
+ |
mode: e.mode, |
|
371 |
+ |
content: FileContent::Text { |
|
372 |
+ |
bytes, |
|
373 |
+ |
materialized, |
|
374 |
+ |
}, |
|
375 |
+ |
}, |
|
376 |
+ |
None if skip.contains(&e.node) => { |
|
377 |
+ |
unrendered.insert(path.clone()); |
|
378 |
+ |
FileState { |
|
379 |
+ |
node: e.node, |
|
380 |
+ |
kind: e.kind, |
|
381 |
+ |
mode: e.mode, |
|
382 |
+ |
content: FileContent::Text { |
|
383 |
+ |
bytes: Vec::new(), |
|
384 |
+ |
materialized: Materialized::default(), |
|
385 |
+ |
}, |
|
386 |
+ |
} |
|
387 |
+ |
} |
|
388 |
+ |
None => { |
|
389 |
+ |
// Neither asked for nor known unchanged: render it |
|
390 |
+ |
// from a built graph, as the whole-tree path does. |
|
391 |
+ |
self.load_graphs_of(base, &[e.node])?; |
|
392 |
+ |
base.entry_state(e) |
|
393 |
+ |
} |
|
394 |
+ |
} |
|
395 |
+ |
} else { |
|
396 |
+ |
base.entry_state(e) |
|
397 |
+ |
}; |
|
398 |
+ |
files.insert(path.clone(), f); |
|
399 |
+ |
} |
|
400 |
+ |
Ok(crate::state::Snapshot { |
|
401 |
+ |
tree: view, |
|
402 |
+ |
files, |
|
403 |
+ |
unrendered, |
|
404 |
+ |
}) |
|
405 |
+ |
} |
|
406 |
+ |
} |
|
407 |
+ |
|
| 294 |
408 |
|
fn git(dir: &Path, args: &[&str]) -> Result<String> { |
| 295 |
409 |
|
let out = Command::new("git") |
| 296 |
410 |
|
.arg("-C") |
|
|
… |
654 unchanged lines |
| 951 |
1065 |
|
.collect(); |
| 952 |
1066 |
|
crate::timing::add("render: same and need", clock.elapsed()); |
| 953 |
1067 |
|
let t = std::time::Instant::now(); |
| 954 |
|
- |
self.load_graphs_of(&mut base, &need)?; |
| 955 |
|
- |
crate::timing::add("render: load graphs, whole tree", t.elapsed()); |
| 956 |
|
- |
let t = std::time::Instant::now(); |
| 957 |
|
- |
let snap = base.materialize_except(&skip); |
| 958 |
|
- |
crate::timing::add("render: materialize, whole tree", t.elapsed()); |
|
1068 |
+ |
let snap = if need.len() > merge_batch() { |
|
1069 |
+ |
// A merge onto a base with no built graphs: thousands |
|
1070 |
+ |
// of files, whose graphs would not fit at once. |
|
1071 |
+ |
self.materialize_batched(&mut base, &need, &skip)? |
|
1072 |
+ |
} else { |
|
1073 |
+ |
self.load_graphs_of(&mut base, &need)?; |
|
1074 |
+ |
base.materialize_except(&skip) |
|
1075 |
+ |
}; |
|
1076 |
+ |
crate::timing::add("render: whole tree", t.elapsed()); |
| 959 |
1077 |
|
snap |
| 960 |
1078 |
|
}; |
| 961 |
1079 |
|
|
|
|
… |
745 unchanged lines |