fix(format): resolve each hard link once
A hard link's target may go through other hard links, and each was
resolved again every time a path went through it. With each link's
target naming the previous link twice (g/s{i} -> /g/s{i-1}/s{i-1}) the
work doubled per link: finish() took 46 s for 26 links in a debug build,
and 60 would never finish. Resolved links are now remembered, so the work
is linear in the links, and a hard link met again while it is being
resolved is reported as a cycle by name. The depth limit (64) still bounds
the recursion through links not yet resolved.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -827,3 +827,52 @@ fn a_link_too_big_for_dense_storage_is_an_error() {
|
||||
assert_eq!(out, "[11, 65001]");
|
||||
h5dump_ok(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_hard_links_resolve_in_linear_time() {
|
||||
skip_if_no_python!();
|
||||
// Each link's target goes through the previous link twice. Resolving
|
||||
// them without remembering resolved links doubled the work per link:
|
||||
// 26 links took 46 s in a debug build, so 60 would never finish.
|
||||
fn chain(reverse: bool) -> FileBuilder {
|
||||
let mut b = FileBuilder::new();
|
||||
let mut g = b.create_group("g");
|
||||
g.create_dataset("v").with_i32_data(&[5]);
|
||||
b.add_group(g.finish());
|
||||
let mut order: Vec<usize> = (0..60).collect();
|
||||
if reverse {
|
||||
order.reverse();
|
||||
}
|
||||
for i in order {
|
||||
if i == 0 {
|
||||
b.add_hard_link("g/s0", "/g");
|
||||
} else {
|
||||
b.add_hard_link(&format!("g/s{i}"), &format!("/g/s{}/s{}", i - 1, i - 1));
|
||||
}
|
||||
}
|
||||
b
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let bytes = [false, true].map(|r| chain(r).finish().unwrap());
|
||||
tx.send(bytes).unwrap();
|
||||
});
|
||||
let [forward, reverse] = rx
|
||||
.recv_timeout(std::time::Duration::from_secs(60))
|
||||
.expect("resolving 60 chained hard links took over a minute");
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
for (name, bytes) in [("forward.h5", forward), ("reverse.h5", reverse)] {
|
||||
let path = dir.path().join(name).display().to_string();
|
||||
std::fs::write(&path, bytes).unwrap();
|
||||
let out = h5py(
|
||||
&path,
|
||||
"with h5py.File(path, 'r') as f:\n\
|
||||
\x20 print(json.dumps([h5py.h5o.get_info(f['g'].id).rc, len(f['g']),\n\
|
||||
\x20 int(f['g/s59/s30/s0/v'][0]), f['g/s59'] == f['g']]))",
|
||||
);
|
||||
assert_eq!(out, "[61, 61, 5, true]", "{name}");
|
||||
let f = File::open(&path).unwrap();
|
||||
assert_eq!(f.dataset("g/s59/s0/v").unwrap().read_i32().unwrap(), [5]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user