Chunked reads beat an h5py process pool; unlimited writer B-trees; Blosc2; 599/697 conformance #16

Merged
osobh merged 48 commits from feat/p2b-scale into main 2026-09-26 17:42:16 +00:00
2 changed files with 31 additions and 11 deletions
Showing only changes of commit 9e59499c56 - Show all commits
+7 -1
View File
@@ -146,7 +146,13 @@ for rel in files:
if a.get("kind") != b.get("kind") and "error" not in b and "error" not in a: if a.get("kind") != b.get("kind") and "error" not in b and "error" not in a:
issues.append(("mismatch", f"{p}: kind {a.get('kind')} vs ours {b.get('kind')}", "kind", b)) issues.append(("mismatch", f"{p}: kind {a.get('kind')} vs ours {b.get('kind')}", "kind", b))
ok = False ok = False
# h5py could not open the object at all: it read none of its
# attributes or links, so there is nothing to compare ours with
# (the object's own error is compared above and below).
ref_unopened = a.get("kind") == "unknown" and "error" in a
for k in ("error", "list_error", "attrs_error"): for k in ("error", "list_error", "attrs_error"):
if ref_unopened and k != "error":
continue
if k in b and k not in a: if k in b and k not in a:
issues.append(("our-error", f"{p}: {k}: {b[k]}", b[k], b)) issues.append(("our-error", f"{p}: {k}: {b[k]}", b[k], b))
ok = False ok = False
@@ -164,7 +170,7 @@ for rel in files:
issues.append(("mismatch", f"{p}: values differ (h5py {a.get('dtype')} vs ours {b.get('dtype')})", "values", b | {"ref_head": a.get("head"), "ref_dtype": a.get("dtype")})) issues.append(("mismatch", f"{p}: values differ (h5py {a.get('dtype')} vs ours {b.get('dtype')})", "values", b | {"ref_head": a.get("head"), "ref_dtype": a.get("dtype")}))
ok = False ok = False
ra, oa = a.get("attrs") or {}, b.get("attrs") or {} ra, oa = a.get("attrs") or {}, b.get("attrs") or {}
if "attrs_error" not in b and "attrs_error" not in a: if "attrs_error" not in b and "attrs_error" not in a and not ref_unopened:
for an in sorted(set(ra) | set(oa)): for an in sorted(set(ra) | set(oa)):
x, y = ra.get(an), oa.get(an) x, y = ra.get(an), oa.get(an)
if x is None: if x is None:
+24 -10
View File
@@ -111,8 +111,11 @@ def note_conversion(tid, dt, rec):
def hash_values(arr, dt, rec): def hash_values(arr, dt, rec):
if dt.subdtype is not None: # h5py expands an HDF5 array element type into trailing array dims, a
# h5py expands an HDF5 array element type into trailing array dims # nested array type (an array of arrays) into all of them. Converting the
# expanded array back to the inner subarray type would broadcast every
# element into a whole subarray, so strip every level.
while dt.subdtype is not None:
dt = dt.subdtype[0] dt = dt.subdtype[0]
arr = np.asarray(arr, dtype=dt) arr = np.asarray(arr, dtype=dt)
if simple(dt): if simple(dt):
@@ -173,9 +176,13 @@ def main(path):
return return
objects = [] objects = []
seen = set() seen = set()
stack = [("/", None)] # Objects h5py cannot open have no ObjectID to deduplicate by; they are
# deduplicated by the address their hard link points at instead, as the
# probe deduplicates every object by header address.
seen_unopenable = set()
stack = [("/", None, None)]
while stack: while stack:
p, obj = stack.pop() p, obj, link_addr = stack.pop()
if len(objects) >= MAX_OBJECTS: if len(objects) >= MAX_OBJECTS:
top["truncated"] = True top["truncated"] = True
break break
@@ -185,6 +192,10 @@ def main(path):
obj = f[p] obj = f[p]
key = hash(obj.id) # h5py ObjectID hash = (fileno, object address/token) key = hash(obj.id) # h5py ObjectID hash = (fileno, object address/token)
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
if link_addr is not None:
if link_addr in seen_unopenable:
continue
seen_unopenable.add(link_addr)
rec["kind"] = "unknown" rec["kind"] = "unknown"
rec["error"] = err(e) rec["error"] = err(e)
objects.append(rec) objects.append(rec)
@@ -232,15 +243,18 @@ def main(path):
base = "" if p == "/" else p base = "" if p == "/" else p
kids = [] kids = []
for n in names: for n in names:
# The link's own type: `obj.get(n, getlink=True)` reports
# a user-defined link (type 64-255) as a HardLink.
try: try:
link = obj.get(n, getlink=True) info = obj.id.links.get_info(n.encode("utf-8", "surrogateescape"))
except Exception: # noqa: BLE001 except Exception: # noqa: BLE001
link = None info = None
if link is not None and not isinstance(link, h5py.HardLink): if info is not None and info.type != h5py.h5l.TYPE_HARD:
continue continue
kids.append(f"{base}/{n}") addr = info.u if info is not None else None
for k in reversed(kids): kids.append((f"{base}/{n}", addr))
stack.append((k, None)) for k, addr in reversed(kids):
stack.append((k, None, addr))
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
rec["list_error"] = err(e) rec["list_error"] = err(e)
objects.append(rec) objects.append(rec)