diff --git a/.github/workflows/cibuildwheels.yml b/.github/workflows/cibuildwheels.yml index 1d7a632a..ed69f764 100644 --- a/.github/workflows/cibuildwheels.yml +++ b/.github/workflows/cibuildwheels.yml @@ -92,7 +92,7 @@ jobs: arch: amd64 - name: Build wheels - uses: pypa/cibuildwheel@v3.3 + uses: pypa/cibuildwheel@v3.4 - name: Make sdist if: ${{ matrix.os == 'ubuntu-latest' }} diff --git a/ANNOUNCE.rst b/ANNOUNCE.rst index f1551f82..69d06955 100644 --- a/ANNOUNCE.rst +++ b/ANNOUNCE.rst @@ -1,7 +1,7 @@ -Announcing Python-Blosc2 4.1.2 +Announcing Python-Blosc2 4.0.0 =============================== -This is patch release which updates the ``c-blosc2`` version to fix some memory leaks. +This is patch release which updates the ``miniexpr`` version to fix a bug for ubuntu ARM64 failure. You can think of Python-Blosc2 4.x as an extension of NumPy/numexpr that: diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d921567..6a105a1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,7 +119,8 @@ else() include(FetchContent) FetchContent_Declare(blosc2 GIT_REPOSITORY https://github.com/Blosc/c-blosc2 - GIT_TAG 1386ef42f58b61c876edf714a2af84bd7b59dc5d # v2.23.1 + GIT_TAG 9200990b189c8357e5517860cfa9ef09cb117eae + # SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../c-blosc2 ) FetchContent_MakeAvailable(blosc2) include_directories("${blosc2_SOURCE_DIR}/include") diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 84602cf0..49c74c5d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,12 +1,20 @@ # Release notes - ## Changes from 4.1.2 to 4.1.3 - - XXX version-specific blurb XXX - ## Changes from 4.1.1 to 4.1.2 -- Update `c-blosc2` version +- A new fast path for src/blosc2/linalg.py that uses the matmul prefilter machinery in src/blosc2/blosc2_ext.pyx. + - The fast path is only used for supported cases: + - blosc2.NDArray inputs + - 2-D only + - floating-point only + - matching dtypes + - aligned chunk/block layouts that satisfy the current kernel assumptions + - All other valid cases fall back to the existing chunk-by-chunk implementation in src/blosc2/linalg.py. + - Some benchmarks for the supported cases show significant speedups over the chunked implementation: + - aligned 400x400 float32: about 3.7x faster over chunked + - aligned 400x400 float64: about 3.0x + - aligned 800x800 float32: about 1.5x + - misaligned case: auto correctly stays on chunked ## Changes from 4.1.0 to 4.1.1 diff --git a/bench/batch_store.py b/bench/batch_store.py new file mode 100644 index 00000000..7b84370d --- /dev/null +++ b/bench/batch_store.py @@ -0,0 +1,170 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import argparse +import random +import statistics +import time + +import blosc2 + + +URLPATH = "bench_batch_store.b2b" +NBATCHES = 10_000 +OBJECTS_PER_BATCH = 100 +TOTAL_OBJECTS = NBATCHES * OBJECTS_PER_BATCH +BLOCKSIZE_MAX = 32 +N_RANDOM_READS = 1_000 + + +def make_rgb(batch_index: int, item_index: int) -> dict[str, int]: + global_index = batch_index * OBJECTS_PER_BATCH + item_index + return { + "red": batch_index, + "green": item_index, + "blue": global_index, + } + + +def make_batch(batch_index: int) -> list[dict[str, int]]: + return [make_rgb(batch_index, item_index) for item_index in range(OBJECTS_PER_BATCH)] + + +def expected_entry(batch_index: int, item_index: int) -> dict[str, int]: + return { + "red": batch_index, + "green": item_index, + "blue": batch_index * OBJECTS_PER_BATCH + item_index, + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Benchmark BatchStore single-entry reads.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("--codec", type=str, default="ZSTD", choices=[codec.name for codec in blosc2.Codec]) + parser.add_argument("--clevel", type=int, default=5) + parser.add_argument("--serializer", type=str, default="msgpack", choices=["msgpack", "arrow"]) + parser.add_argument("--use-dict", action="store_true", help="Enable dictionaries for ZSTD/LZ4/LZ4HC codecs.") + parser.add_argument("--in-mem", action="store_true", help="Keep the BatchStore purely in memory.") + return parser + + +def build_store( + codec: blosc2.Codec, clevel: int, use_dict: bool, serializer: str, in_mem: bool +) -> blosc2.BatchStore | None: + if in_mem: + storage = blosc2.Storage(mode="w") + store = blosc2.BatchStore( + storage=storage, + max_blocksize=BLOCKSIZE_MAX, + serializer=serializer, + cparams={ + "codec": codec, + "clevel": clevel, + "use_dict": use_dict and codec in (blosc2.Codec.ZSTD, blosc2.Codec.LZ4, blosc2.Codec.LZ4HC), + }, + ) + for batch_index in range(NBATCHES): + store.append(make_batch(batch_index)) + return store + + blosc2.remove_urlpath(URLPATH) + storage = blosc2.Storage(urlpath=URLPATH, mode="w", contiguous=True) + cparams = { + "codec": codec, + "clevel": clevel, + "use_dict": use_dict and codec in (blosc2.Codec.ZSTD, blosc2.Codec.LZ4, blosc2.Codec.LZ4HC), + } + with blosc2.BatchStore( + storage=storage, max_blocksize=BLOCKSIZE_MAX, serializer=serializer, cparams=cparams + ) as store: + for batch_index in range(NBATCHES): + store.append(make_batch(batch_index)) + return None + + +def measure_random_reads(store: blosc2.BatchStore) -> tuple[list[tuple[int, int, int, dict[str, int]]], list[int]]: + rng = random.Random(2024) + samples: list[tuple[int, int, int, dict[str, int]]] = [] + timings_ns: list[int] = [] + + for _ in range(N_RANDOM_READS): + batch_index = rng.randrange(len(store)) + item_index = rng.randrange(OBJECTS_PER_BATCH) + t0 = time.perf_counter_ns() + value = store[batch_index][item_index] + timings_ns.append(time.perf_counter_ns() - t0) + if value != expected_entry(batch_index, item_index): + raise RuntimeError(f"Value mismatch at batch={batch_index}, item={item_index}") + samples.append((timings_ns[-1], batch_index, item_index, value)) + + return samples, timings_ns + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + codec = blosc2.Codec[args.codec] + use_dict = args.use_dict and codec in (blosc2.Codec.ZSTD, blosc2.Codec.LZ4, blosc2.Codec.LZ4HC) + + mode_label = "in-memory" if args.in_mem else "persistent" + article = "an" if args.in_mem else "a" + print(f"Building {article} {mode_label} BatchStore with 1,000,000 RGB dicts and timing 1,000 random scalar reads...") + print(f" codec: {codec.name}") + print(f" clevel: {args.clevel}") + print(f" serializer: {args.serializer}") + print(f" use_dict: {use_dict}") + print(f" in_mem: {args.in_mem}") + t0 = time.perf_counter() + store = build_store( + codec=codec, clevel=args.clevel, use_dict=use_dict, serializer=args.serializer, in_mem=args.in_mem + ) + build_time_s = time.perf_counter() - t0 + if args.in_mem: + assert store is not None + read_store = store + else: + read_store = blosc2.BatchStore(urlpath=URLPATH, mode="r", contiguous=True, max_blocksize=BLOCKSIZE_MAX) + samples, timings_ns = measure_random_reads(read_store) + t0 = time.perf_counter() + checksum = 0 + nitems = 0 + for item in read_store.iter_items(): + checksum += item["blue"] + nitems += 1 + iter_time_s = time.perf_counter() - t0 + + print() + print("BatchStore benchmark") + print(f" build time: {build_time_s:.3f} s") + print(f" batches: {len(read_store)}") + print(f" items: {TOTAL_OBJECTS}") + print(f" max_blocksize: {read_store.max_blocksize}") + print() + print(read_store.info) + print(f"Random scalar reads: {N_RANDOM_READS}") + print(f" mean: {statistics.fmean(timings_ns) / 1_000:.2f} us") + print(f" max: {max(timings_ns) / 1_000:.2f} us") + print(f" min: {min(timings_ns) / 1_000:.2f} us") + print(f"Item iteration via iter_items(): {iter_time_s:.3f} s") + print(f" per item: {iter_time_s * 1_000_000 / nitems:.2f} us") + print(f" checksum: {checksum}") + print("Sample reads:") + for timing_ns, batch_index, item_index, value in samples[:5]: + print(f" {timing_ns / 1_000:.2f} us -> read_store[{batch_index}][{item_index}] = {value}") + if args.in_mem: + print("BatchStore kept in memory") + else: + print(f"BatchStore file at: {read_store.urlpath}") + + +if __name__ == "__main__": + main() diff --git a/bench/ndarray/matmul_path_compare.py b/bench/ndarray/matmul_path_compare.py new file mode 100644 index 00000000..82ef7f6d --- /dev/null +++ b/bench/ndarray/matmul_path_compare.py @@ -0,0 +1,220 @@ +import argparse +import json +import statistics +import time +import warnings + +import numpy as np + +import blosc2 +import blosc2.linalg as linalg + + +def parse_int_tuple(value: str) -> tuple[int, ...]: + return tuple(int(item.strip()) for item in value.split(",") if item.strip()) + + +def build_arrays( + shape_a: tuple[int, ...], + shape_b: tuple[int, ...], + dtype: np.dtype, + chunks_a: tuple[int, ...] | None, + chunks_b: tuple[int, ...] | None, + blocks_a: tuple[int, ...] | None, + blocks_b: tuple[int, ...] | None, +): + a_np = np.ones(shape_a, dtype=dtype) + b_np = np.full(shape_b, 2, dtype=dtype) + a = blosc2.asarray(a_np, chunks=chunks_a, blocks=blocks_a) + b = blosc2.asarray(b_np, chunks=chunks_b, blocks=blocks_b) + return a, b, a_np, b_np + + +def expected_gflops(shape_a: tuple[int, ...], shape_b: tuple[int, ...], elapsed: float) -> float | None: + if elapsed <= 0 or len(shape_a) < 2 or len(shape_b) < 2: + return None + m = shape_a[-2] + k = shape_a[-1] + n = shape_b[-1] + batch = int(np.prod(np.broadcast_shapes(shape_a[:-2], shape_b[:-2]))) if len(shape_a) > 2 or len(shape_b) > 2 else 1 + flops = 2 * batch * m * n * k + return flops / elapsed / 1e9 + + +def set_path_mode(mode: str) -> bool: + original = linalg.try_miniexpr + if mode == "chunked": + linalg.try_miniexpr = False + elif mode == "fast": + linalg.try_miniexpr = True + elif mode == "auto": + linalg.try_miniexpr = original + else: + raise ValueError(f"unknown mode: {mode}") + return original + + +def run_case( + mode: str, + repeats: int, + shape_a: tuple[int, ...], + shape_b: tuple[int, ...], + dtype: np.dtype, + chunks_a: tuple[int, ...] | None, + chunks_b: tuple[int, ...] | None, + blocks_a: tuple[int, ...] | None, + blocks_b: tuple[int, ...] | None, + chunks_out: tuple[int, ...] | None, + blocks_out: tuple[int, ...] | None, +): + a, b, a_np, b_np = build_arrays(shape_a, shape_b, dtype, chunks_a, chunks_b, blocks_a, blocks_b) + with warnings.catch_warnings(): + # NumPy + Accelerate can emit spurious matmul RuntimeWarnings on macOS arm64. + warnings.simplefilter("ignore", RuntimeWarning) + expected = np.matmul(a_np, b_np) + original_flag = set_path_mode(mode) + original_set_pref_matmul = blosc2.NDArray._set_pref_matmul + selected_paths = [] + times = [] + result = None + + def wrapped_set_pref_matmul(self, inputs, fp_accuracy): + selected_paths.append("fast") + return original_set_pref_matmul(self, inputs, fp_accuracy) + + blosc2.NDArray._set_pref_matmul = wrapped_set_pref_matmul + try: + for _ in range(repeats): + before = len(selected_paths) + t0 = time.perf_counter() + with warnings.catch_warnings(): + # NumPy + Accelerate can emit spurious matmul RuntimeWarnings on macOS arm64. + warnings.simplefilter("ignore", RuntimeWarning) + result = blosc2.matmul(a, b, chunks=chunks_out, blocks=blocks_out) + times.append(time.perf_counter() - t0) + if len(selected_paths) == before: + selected_paths.append("chunked") + finally: + blosc2.NDArray._set_pref_matmul = original_set_pref_matmul + linalg.try_miniexpr = original_flag + + if result is None: + raise RuntimeError("matmul did not produce a result") + + actual = result[:] + np.testing.assert_allclose(actual, expected, rtol=1e-6, atol=1e-6) + + best = min(times) + median = statistics.median(times) + return { + "mode": mode, + "times_s": times, + "best_s": best, + "median_s": median, + "gflops_best": expected_gflops(shape_a, shape_b, best), + "gflops_median": expected_gflops(shape_a, shape_b, median), + "correct": True, + "selected_paths": selected_paths, + "selected_path": selected_paths[0] if selected_paths and len(set(selected_paths)) == 1 else "mixed", + } + + +def main() -> None: + parser = argparse.ArgumentParser(description="Compare chunked and fast blosc2.matmul paths.") + parser.add_argument("--shape-a", default="400,400", help="Comma-separated shape for A.") + parser.add_argument("--shape-b", default="400,400", help="Comma-separated shape for B.") + parser.add_argument("--dtype", default="float32", choices=["float32", "float64", "int32", "int64"]) + parser.add_argument("--chunks-a", default="200,200", help="Comma-separated chunk shape for A.") + parser.add_argument("--chunks-b", default="200,200", help="Comma-separated chunk shape for B.") + parser.add_argument("--blocks-a", default="100,100", help="Comma-separated block shape for A.") + parser.add_argument("--blocks-b", default="100,100", help="Comma-separated block shape for B.") + parser.add_argument("--chunks-out", default="200,200", help="Comma-separated chunk shape for output.") + parser.add_argument("--blocks-out", default="100,100", help="Comma-separated block shape for output.") + parser.add_argument("--repeats", type=int, default=250) + parser.add_argument("--modes", nargs="+", default=["chunked", "fast", "auto"], choices=["chunked", "fast", "auto"]) + parser.add_argument("--json", action="store_true", help="Emit full JSON instead of a compact text summary.") + args = parser.parse_args() + + shape_a = parse_int_tuple(args.shape_a) + shape_b = parse_int_tuple(args.shape_b) + chunks_a = parse_int_tuple(args.chunks_a) if args.chunks_a else None + chunks_b = parse_int_tuple(args.chunks_b) if args.chunks_b else None + blocks_a = parse_int_tuple(args.blocks_a) if args.blocks_a else None + blocks_b = parse_int_tuple(args.blocks_b) if args.blocks_b else None + chunks_out = parse_int_tuple(args.chunks_out) if args.chunks_out else None + blocks_out = parse_int_tuple(args.blocks_out) if args.blocks_out else None + dtype = np.dtype(args.dtype) + + results = [] + for mode in args.modes: + results.append( + run_case( + mode, + args.repeats, + shape_a, + shape_b, + dtype, + chunks_a, + chunks_b, + blocks_a, + blocks_b, + chunks_out, + blocks_out, + ) + ) + + summary = { + "shape_a": shape_a, + "shape_b": shape_b, + "dtype": str(dtype), + "chunks_a": chunks_a, + "chunks_b": chunks_b, + "blocks_a": blocks_a, + "blocks_b": blocks_b, + "chunks_out": chunks_out, + "blocks_out": blocks_out, + "results": results, + } + + best_by_mode = {item["mode"]: item["best_s"] for item in results} + if "chunked" in best_by_mode and "fast" in best_by_mode: + summary["speedup_fast_vs_chunked"] = best_by_mode["chunked"] / best_by_mode["fast"] + + if args.json: + print(json.dumps(summary, indent=2, sort_keys=True)) + return + + print( + "case", + json.dumps( + { + "shape_a": shape_a, + "shape_b": shape_b, + "dtype": str(dtype), + "chunks_out": chunks_out, + "blocks_out": blocks_out, + }, + sort_keys=True, + ), + ) + for item in results: + print( + "result", + json.dumps( + { + "mode": item["mode"], + "best_s": round(item["best_s"], 6), + "median_s": round(item["median_s"], 6), + "gflops_best": None if item["gflops_best"] is None else round(item["gflops_best"], 3), + "correct": item["correct"], + "selected_path": item["selected_path"], + }, + sort_keys=True, + ), + ) + if "speedup_fast_vs_chunked" in summary: + print("speedup", json.dumps({"fast_vs_chunked": round(summary["speedup_fast_vs_chunked"], 3)}, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/bench/ndarray/multithreaded_matmul_bench.py b/bench/ndarray/multithreaded_matmul_bench.py new file mode 100644 index 00000000..810527c6 --- /dev/null +++ b/bench/ndarray/multithreaded_matmul_bench.py @@ -0,0 +1,48 @@ +import blosc2 +import numpy as np +import time + +N = 10000 +ndim = 2 +ashape = (N,) * ndim +bshape = ashape +dtype = np.float64 + +achunks = (1000, 1000) +bchunks = (achunks[1], achunks[0]) +ablocks = (200, 200) +bblocks = (ablocks[1], ablocks[0]) +outblocks = (ablocks[0], bblocks[1]) +outchunks = (achunks[0], bchunks[1]) +# a = blosc2.linspace(0, 1, dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks) +# b = blosc2.linspace(0, 1, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks) +a = blosc2.ones(dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks) +b = blosc2.full(fill_value=2, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks) + +a_np = a[:] +b_np = b[:] +tic = time.time() +np_res = np.matmul(a_np, b_np) +print(f'numpy finished in {time.time()-tic} s') + +tic = time.time() +b2_res = blosc2.matmul(a, b, blocks=outblocks, chunks=outchunks) +print(f'blosc2 multithreaded finished in {time.time()-tic} s') + +tic = time.time() +b2_res = blosc2.matmul(a, b) +print(f'blosc2 normal finished in {time.time()-tic} s') + +achunks = None #(1000, 1000) +bchunks = None #(achunks[1], achunks[0]) +ablocks = None #(200, 200) +bblocks = None #(ablocks[1], ablocks[0]) +outblocks = None #(ablocks[0], bblocks[1]) +outchunks = None #(achunks[0], bchunks[1]) +# a = blosc2.linspace(0, 1, dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks) +# b = blosc2.linspace(0, 1, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks) +a = blosc2.ones(dtype=dtype, shape=ashape, chunks=achunks, blocks=ablocks) +b = blosc2.full(fill_value=2, dtype=dtype, shape=bshape, chunks=bchunks, blocks=bblocks) +tic = time.time() +b2_res = blosc2.matmul(a, b, blocks=outblocks, chunks=outchunks) +print(f'blosc2 normal with default chunks etc. finished in {time.time()-tic} s') diff --git a/bench/ndarray/resize.py b/bench/ndarray/resize.py new file mode 100644 index 00000000..24525d80 --- /dev/null +++ b/bench/ndarray/resize.py @@ -0,0 +1,131 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import argparse +import os +import time + +import numpy as np + +import blosc2 + + +def parse_nitems(text: str) -> int: + suffixes = {"k": 1_000, "m": 1_000_000, "g": 1_000_000_000} + text = text.strip().lower() + if text[-1:] in suffixes: + return int(float(text[:-1]) * suffixes[text[-1]]) + return int(text) + + +def sizeof_path(path: str) -> int: + if os.path.isdir(path): + total = 0 + for root, _, files in os.walk(path): + for name in files: + total += os.path.getsize(os.path.join(root, name)) + return total + return os.path.getsize(path) + + +def format_bytes(nbytes: int) -> str: + units = ["B", "KiB", "MiB", "GiB", "TiB"] + value = float(nbytes) + for unit in units: + if value < 1024 or unit == units[-1]: + return f"{value:.2f} {unit}" + value /= 1024 + return f"{nbytes} B" + + +def pick_layout(nitems: int) -> tuple[tuple[int], tuple[int]]: + chunks = (max(1, min(nitems, 16_384)),) + blocks = (max(1, min(chunks[0], 256)),) + return chunks, blocks + + +def create_extended_array( + path: str, nitems: int, dtype: np.dtype, chunks: tuple[int], blocks: tuple[int], bsize: int +) -> blosc2.NDArray: + array = blosc2.empty((0,), dtype=dtype, chunks=chunks, blocks=blocks, urlpath=path, mode="w") + for start in range(0, nitems, bsize): + stop = min(start + bsize, nitems) + array.resize((stop,)) + array[start:stop] = np.arange(start, stop, dtype=dtype) + return array + + +def create_full_array(path: str, data: np.ndarray, chunks: tuple[int], blocks: tuple[int]) -> blosc2.NDArray: + return blosc2.asarray(data, chunks=chunks, blocks=blocks, urlpath=path, mode="w") + + +def time_random_access(array: blosc2.NDArray, indices: np.ndarray) -> tuple[float, int]: + total = 0 + t0 = time.perf_counter_ns() + for index in indices: + total += int(array[int(index)]) + elapsed_ns = time.perf_counter_ns() - t0 + return elapsed_ns / len(indices) / 1_000_000, total + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Compare resizing an on-disk NDArray in batches vs creating it in one go." + ) + parser.add_argument("--nitems", type=parse_nitems, default=parse_nitems("1M")) + parser.add_argument("--bsize", type=parse_nitems, default=parse_nitems("1K")) + parser.add_argument("--samples", type=int, default=10_000) + parser.add_argument("--seed", type=int, default=0) + parser.add_argument("--dtype", default="int64") + parser.add_argument("--extended-path", default="resize-batched.b2nd") + parser.add_argument("--full-path", default="resize-one-go.b2nd") + args = parser.parse_args() + + dtype = np.dtype(args.dtype) + chunks, blocks = pick_layout(args.nitems) + data = np.arange(args.nitems, dtype=dtype) + rng = np.random.default_rng(args.seed) + indices = rng.integers(0, args.nitems, size=args.samples) + + for path in (args.extended_path, args.full_path): + blosc2.remove_urlpath(path) + + t0 = time.perf_counter() + extended = create_extended_array(args.extended_path, args.nitems, dtype, chunks, blocks, args.bsize) + extend_time = time.perf_counter() - t0 + + t0 = time.perf_counter() + full = create_full_array(args.full_path, data, chunks, blocks) + full_time = time.perf_counter() - t0 + + extended_size = sizeof_path(args.extended_path) + full_size = sizeof_path(args.full_path) + + extended_access_ns, extended_checksum = time_random_access(extended, indices) + full_access_ns, full_checksum = time_random_access(full, indices) + + print(f"nitems: {args.nitems:_}") + print(f"dtype: {dtype}") + print(f"chunks: {chunks}") + print(f"blocks: {blocks}") + print(f"batch size: {args.bsize:_}") + print(f"resize build time: {extend_time:.3f} s") + print(f"one-go build time: {full_time:.3f} s") + print(f"resized array file size: {extended_size} bytes ({format_bytes(extended_size)})") + print(f"one-go array file size: {full_size} bytes ({format_bytes(full_size)})") + print(f"random access samples: {args.samples:_}") + print(f"resized array random access: {extended_access_ns:.6f} ms/item") + print(f"one-go array random access: {full_access_ns:.6f} ms/item") + + if extended_checksum != full_checksum: + raise RuntimeError("Random-access checksums differ between arrays") + + +if __name__ == "__main__": + main() diff --git a/bench/ndarray/stringops_bench.py b/bench/ndarray/stringops_bench.py index c983a0f2..5f97aac3 100644 --- a/bench/ndarray/stringops_bench.py +++ b/bench/ndarray/stringops_bench.py @@ -12,7 +12,7 @@ import time import numpy as np import blosc2 -from blosc2.lazyexpr import _toggle_miniexpr +from blosc2.utils import _toggle_miniexpr # nparr = np.random.randint(low=0, high=128, size=(N, 10), dtype=np.uint32) # nparr = nparr.view('S40').astype('U10') diff --git a/doc/getting_started/tutorials.rst b/doc/getting_started/tutorials.rst index 35f347d4..563ba8ea 100644 --- a/doc/getting_started/tutorials.rst +++ b/doc/getting_started/tutorials.rst @@ -16,3 +16,4 @@ Tutorials tutorials/08.schunk-slicing_and_beyond tutorials/09.ucodecs-ufilters tutorials/10.prefilters + tutorials/11.vlarray diff --git a/doc/getting_started/tutorials/11.vlarray.ipynb b/doc/getting_started/tutorials/11.vlarray.ipynb new file mode 100644 index 00000000..e78733fb --- /dev/null +++ b/doc/getting_started/tutorials/11.vlarray.ipynb @@ -0,0 +1,325 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with VLArray\n", + "\n", + "A `VLArray` is a list-like container for variable-length Python values backed by a single `SChunk`. Each entry is stored in its own compressed chunk, and values are serialized with msgpack before reaching storage.\n", + "\n", + "This makes `VLArray` a good fit for heterogeneous, variable-length payloads such as small dictionaries, strings, tuples, byte blobs, or nested list/dict structures." + ], + "id": "ceb4789a488cc07f" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.563663Z", + "start_time": "2026-03-14T16:57:57.294290Z" + } + }, + "source": [ + "import blosc2\n", + "\n", + "\n", + "def show(label, value):\n", + " print(f\"{label}: {value}\")\n", + "\n", + "\n", + "urlpath = \"vlarray_tutorial.b2frame\"\n", + "copy_path = \"vlarray_tutorial_copy.b2frame\"\n", + "blosc2.remove_urlpath(urlpath)\n", + "blosc2.remove_urlpath(copy_path)" + ], + "id": "f264f2e4bcb57029", + "outputs": [], + "execution_count": 1 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating and populating a VLArray\n", + "\n", + "Entries can be appended one by one or in batches with `extend()`. The container accepts the msgpack-safe Python types supported by the implementation: `bytes`, `str`, `int`, `float`, `bool`, `None`, `list`, `tuple`, and `dict`." + ], + "id": "24ceae332dfa437" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.609603Z", + "start_time": "2026-03-14T16:57:57.569987Z" + } + }, + "source": [ + "vla = blosc2.VLArray(urlpath=urlpath, mode=\"w\")\n", + "vla.append({\"name\": \"alpha\", \"count\": 1})\n", + "vla.extend([b\"bytes\", (\"a\", 2), [\"x\", \"y\"], 42, None])\n", + "vla.insert(1, \"between\")\n", + "\n", + "show(\"Initial entries\", list(vla))\n", + "show(\"Length\", len(vla))" + ], + "id": "10e4e9ce600cda9d", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initial entries: [{'name': 'alpha', 'count': 1}, 'between', b'bytes', ('a', 2), ['x', 'y'], 42, None]\n", + "Length: 7\n" + ] + } + ], + "execution_count": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Indexing and slicing\n", + "\n", + "Indexing behaves like a Python list. Negative indexes are supported, and slice reads return a plain Python list." + ], + "id": "2f2dbe81b7653d8f" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.677796Z", + "start_time": "2026-03-14T16:57:57.623048Z" + } + }, + "source": [ + "show(\"Last entry\", vla[-1])\n", + "show(\"Slice [1:6:2]\", vla[1:6:2])\n", + "show(\"Reverse slice\", vla[::-2])" + ], + "id": "82ea38dca631efb9", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last entry: None\n", + "Slice [1:6:2]: ['between', ('a', 2), 42]\n", + "Reverse slice: [None, ['x', 'y'], b'bytes', {'name': 'alpha', 'count': 1}]\n" + ] + } + ], + "execution_count": 3 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Updating, inserting, and deleting\n", + "\n", + "Single entries can be overwritten by index. Slice assignment follows Python list rules: slices with `step == 1` may resize the container, while extended slices require matching lengths." + ], + "id": "a871bb9b21d6f36c" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.727569Z", + "start_time": "2026-03-14T16:57:57.678936Z" + } + }, + "source": [ + "vla[2:5] = [\"replaced\", {\"nested\": True}]\n", + "show(\"After slice replacement\", list(vla))\n", + "\n", + "vla[::2] = [\"even-0\", \"even-1\", \"even-2\"]\n", + "show(\"After extended-slice update\", list(vla))\n", + "\n", + "del vla[1::3]\n", + "show(\"After slice deletion\", list(vla))\n", + "\n", + "removed = vla.pop()\n", + "show(\"Popped entry\", removed)\n", + "show(\"After pop\", list(vla))" + ], + "id": "e22e4f90499ae02", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After slice replacement: [{'name': 'alpha', 'count': 1}, 'between', 'replaced', {'nested': True}, 42, None]\n", + "After extended-slice update: ['even-0', 'between', 'even-1', {'nested': True}, 'even-2', None]\n", + "After slice deletion: ['even-0', 'even-1', {'nested': True}, None]\n", + "Popped entry: None\n", + "After pop: ['even-0', 'even-1', {'nested': True}]\n" + ] + } + ], + "execution_count": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Copying with new storage or compression parameters\n", + "\n", + "The `copy()` method can duplicate the container into a different storage layout or with different compression settings." + ], + "id": "f41af458cb5faa9f" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.747309Z", + "start_time": "2026-03-14T16:57:57.730015Z" + } + }, + "source": [ + "vla_copy = vla.copy(\n", + " urlpath=copy_path,\n", + " contiguous=False,\n", + " cparams={\"codec\": blosc2.Codec.LZ4, \"clevel\": 5},\n", + ")\n", + "\n", + "show(\"Copied entries\", list(vla_copy))\n", + "show(\"Copy storage is contiguous\", vla_copy.schunk.contiguous)\n", + "show(\"Copy codec\", vla_copy.cparams.codec)" + ], + "id": "6e752260e010272e", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Copied entries: ['even-0', 'even-1', {'nested': True}]\n", + "Copy storage is contiguous: False\n", + "Copy codec: Codec.LZ4\n" + ] + } + ], + "execution_count": 5 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Round-tripping through cframes and reopening from disk\n", + "\n", + "Tagged persistent stores automatically reopen as `VLArray`, and a serialized cframe buffer does too." + ], + "id": "bb576497d4b6f537" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.759998Z", + "start_time": "2026-03-14T16:57:57.748296Z" + } + }, + "source": [ + "cframe = vla.to_cframe()\n", + "restored = blosc2.from_cframe(cframe)\n", + "show(\"from_cframe type\", type(restored).__name__)\n", + "show(\"from_cframe entries\", list(restored))\n", + "\n", + "reopened = blosc2.open(urlpath, mode=\"r\", mmap_mode=\"r\")\n", + "show(\"Reopened type\", type(reopened).__name__)\n", + "show(\"Reopened entries\", list(reopened))" + ], + "id": "42d59dccf6ea9c44", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "from_cframe type: VLArray\n", + "from_cframe entries: ['even-0', 'even-1', {'nested': True}]\n", + "Reopened type: VLArray\n", + "Reopened entries: ['even-0', 'even-1', {'nested': True}]\n" + ] + } + ], + "execution_count": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Clearing and reusing a container\n", + "\n", + "Calling `clear()` resets the backing storage so the container remains ready for new variable-length entries." + ], + "id": "53778312cc1a03bc" + }, + { + "cell_type": "code", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.778160Z", + "start_time": "2026-03-14T16:57:57.761236Z" + } + }, + "source": [ + "scratch = vla.copy()\n", + "scratch.clear()\n", + "scratch.extend([\"fresh\", 123, {\"done\": True}])\n", + "show(\"After clear + extend on in-memory copy\", list(scratch))\n", + "\n", + "blosc2.remove_urlpath(urlpath)\n", + "blosc2.remove_urlpath(copy_path)" + ], + "id": "55b9ea793a41f38a", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After clear + extend on in-memory copy: ['fresh', 123, {'done': True}]\n" + ] + } + ], + "execution_count": 7 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-14T16:57:57.789994Z", + "start_time": "2026-03-14T16:57:57.779434Z" + } + }, + "cell_type": "code", + "source": "", + "id": "34e77790ab2a0f94", + "outputs": [], + "execution_count": 7 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/getting_started/tutorials/12.batchstore.ipynb b/doc/getting_started/tutorials/12.batchstore.ipynb new file mode 100644 index 00000000..52bcc660 --- /dev/null +++ b/doc/getting_started/tutorials/12.batchstore.ipynb @@ -0,0 +1,495 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2c822501cae3b91d", + "metadata": {}, + "source": [ + "# Working with BatchStore\n", + "\n", + "A `BatchStore` is a batch-oriented container for variable-length Python items backed by a single `SChunk`. Each batch is stored in one compressed chunk, and each chunk may contain one or more internal variable-length blocks.\n", + "\n", + "This makes `BatchStore` a good fit when data arrives naturally in batches and you want efficient batch append/update operations together with occasional item-level access inside each batch." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "be8591f8f86952e8", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.190550Z", + "start_time": "2026-03-20T10:24:10.014859Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.329739Z", + "iopub.status.busy": "2026-03-20T10:23:51.329437Z", + "iopub.status.idle": "2026-03-20T10:23:51.556056Z", + "shell.execute_reply": "2026-03-20T10:23:51.555614Z" + } + }, + "outputs": [], + "source": [ + "import blosc2\n", + "\n", + "\n", + "def show(label, value):\n", + " print(f\"{label}: {value}\")\n", + "\n", + "\n", + "urlpath = \"batchstore_tutorial.b2b\"\n", + "copy_path = \"batchstore_tutorial_copy.b2b\"\n", + "blosc2.remove_urlpath(urlpath)\n", + "blosc2.remove_urlpath(copy_path)" + ] + }, + { + "cell_type": "markdown", + "id": "dda38c56e3e63ec1", + "metadata": {}, + "source": [ + "## Creating and populating a BatchStore\n", + "\n", + "A `BatchStore` is indexed by batch. Batches can be appended one by one with `append()` or in bulk with `extend()`. Here we set a small `max_blocksize` just so the internal block structure is easy to observe in `.info`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f8c8a2b7692e7228", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.211954Z", + "start_time": "2026-03-20T10:24:10.191296Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.557338Z", + "iopub.status.busy": "2026-03-20T10:23:51.557245Z", + "iopub.status.idle": "2026-03-20T10:23:51.564920Z", + "shell.execute_reply": "2026-03-20T10:23:51.564578Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Batches: [[{'name': 'alpha', 'count': 1}, {'name': 'beta', 'count': 2}, {'name': 'gamma', 'count': 3}], [{'name': 'delta', 'count': 4}, {'name': 'epsilon', 'count': 5}], [{'name': 'zeta', 'count': 6}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n", + "Number of batches: 5\n" + ] + } + ], + "source": [ + "store = blosc2.BatchStore(urlpath=urlpath, mode=\"w\", contiguous=True, max_blocksize=2)\n", + "store.append(\n", + " [\n", + " {\"name\": \"alpha\", \"count\": 1},\n", + " {\"name\": \"beta\", \"count\": 2},\n", + " {\"name\": \"gamma\", \"count\": 3},\n", + " ]\n", + ")\n", + "store.append(\n", + " [\n", + " {\"name\": \"delta\", \"count\": 4},\n", + " {\"name\": \"epsilon\", \"count\": 5},\n", + " ]\n", + ")\n", + "store.extend(\n", + " [\n", + " [{\"name\": \"zeta\", \"count\": 6}],\n", + " [{\"name\": \"eta\", \"count\": 7}, {\"name\": \"theta\", \"count\": 8}],\n", + " [\n", + " {\"name\": \"iota\", \"count\": 9},\n", + " {\"name\": \"kappa\", \"count\": 10},\n", + " {\"name\": \"lambda\", \"count\": 11},\n", + " ],\n", + " ]\n", + ")\n", + "\n", + "show(\"Batches\", [batch[:] for batch in store])\n", + "show(\"Number of batches\", len(store))" + ] + }, + { + "cell_type": "markdown", + "id": "f57fc5cf2cbaa9ba", + "metadata": {}, + "source": [ + "## Batch and item access\n", + "\n", + "Indexing the store returns a batch. Indexing a batch returns an item inside that batch. Flat item-wise traversal is available through `iter_items()`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "20861d3e348f9df1", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.229980Z", + "start_time": "2026-03-20T10:24:10.213198Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.566000Z", + "iopub.status.busy": "2026-03-20T10:23:51.565919Z", + "iopub.status.idle": "2026-03-20T10:23:51.569765Z", + "shell.execute_reply": "2026-03-20T10:23:51.569439Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First batch: [{'name': 'alpha', 'count': 1}, {'name': 'beta', 'count': 2}, {'name': 'gamma', 'count': 3}]\n", + "Second item in first batch: {'name': 'beta', 'count': 2}\n", + "Slice of second batch: [{'name': 'delta', 'count': 4}]\n", + "All items: [{'name': 'alpha', 'count': 1}, {'name': 'beta', 'count': 2}, {'name': 'gamma', 'count': 3}, {'name': 'delta', 'count': 4}, {'name': 'epsilon', 'count': 5}, {'name': 'zeta', 'count': 6}, {'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}, {'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]\n" + ] + } + ], + "source": [ + "show(\"First batch\", store[0][:])\n", + "show(\"Second item in first batch\", store[0][1])\n", + "show(\"Slice of second batch\", store[1][:1])\n", + "show(\"All items\", list(store.iter_items()))" + ] + }, + { + "cell_type": "markdown", + "id": "eba42acee73bffe3", + "metadata": {}, + "source": [ + "## Updating, inserting, and deleting batches\n", + "\n", + "Mutation is batch-oriented too: you overwrite, insert, delete, and pop whole batches." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "df556f6da8adc369", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.259055Z", + "start_time": "2026-03-20T10:24:10.231589Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.570823Z", + "iopub.status.busy": "2026-03-20T10:23:51.570763Z", + "iopub.status.idle": "2026-03-20T10:23:51.577607Z", + "shell.execute_reply": "2026-03-20T10:23:51.577269Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Popped batch: [{'name': 'zeta', 'count': 6}]\n", + "After updates: [[{'name': 'alpha*', 'count': 10}, {'name': 'beta*', 'count': 20}], [{'name': 'delta*', 'count': 40}, {'name': 'epsilon*', 'count': 50}], [{'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n" + ] + } + ], + "source": [ + "store[1] = [\n", + " {\"name\": \"delta*\", \"count\": 40},\n", + " {\"name\": \"epsilon*\", \"count\": 50},\n", + "]\n", + "store.insert(2, [{\"name\": \"between-a\", \"count\": 99}, {\"name\": \"between-b\", \"count\": 100}])\n", + "removed = store.pop(3)\n", + "del store[0]\n", + "store.insert(0, [{\"name\": \"alpha*\", \"count\": 10}, {\"name\": \"beta*\", \"count\": 20}])\n", + "\n", + "show(\"Popped batch\", removed)\n", + "show(\"After updates\", [batch[:] for batch in store])" + ] + }, + { + "cell_type": "markdown", + "id": "e48791c431156e56", + "metadata": {}, + "source": [ + "## Iteration and summary info\n", + "\n", + "Iterating a `BatchStore` yields batches. The `.info` summary reports both batch-level and internal block-level statistics." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b32d72a68d83673e", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.300526Z", + "start_time": "2026-03-20T10:24:10.259712Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.578504Z", + "iopub.status.busy": "2026-03-20T10:23:51.578433Z", + "iopub.status.idle": "2026-03-20T10:23:51.581563Z", + "shell.execute_reply": "2026-03-20T10:23:51.581191Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Batches via iteration: [[{'name': 'alpha*', 'count': 10}, {'name': 'beta*', 'count': 20}], [{'name': 'delta*', 'count': 40}, {'name': 'epsilon*', 'count': 50}], [{'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n", + "type : BatchStore\n", + "serializer : msgpack\n", + "nbatches : 5 (items per batch: mean=2.20, max=3, min=2)\n", + "nblocks : 6 (items per block: mean=1.83, max=2, min=1)\n", + "nitems : 11\n", + "nbytes : 226 (226 B)\n", + "cbytes : 680 (680 B)\n", + "cratio : 0.33\n", + "cparams : CParams(codec=, codec_meta=0, clevel=5, use_dict=False, typesize=1,\n", + " : nthreads=12, blocksize=0, splitmode=,\n", + " : filters=[, , ,\n", + " : , , ], filters_meta=[0,\n", + " : 0, 0, 0, 0, 0], tuner=)\n", + "dparams : DParams(nthreads=12)\n", + "\n" + ] + } + ], + "source": [ + "show(\"Batches via iteration\", [batch[:] for batch in store])\n", + "print(store.info)" + ] + }, + { + "cell_type": "markdown", + "id": "1d6abe8fe87d3663", + "metadata": {}, + "source": [ + "## Copying and changing storage settings\n", + "\n", + "Like other Blosc2 containers, `BatchStore.copy()` can write a new persistent store while changing storage or compression settings." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "45f878b8f4414a3b", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.334099Z", + "start_time": "2026-03-20T10:24:10.301619Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.582437Z", + "iopub.status.busy": "2026-03-20T10:23:51.582372Z", + "iopub.status.idle": "2026-03-20T10:23:51.590494Z", + "shell.execute_reply": "2026-03-20T10:23:51.590186Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Copied batches: [[{'name': 'alpha*', 'count': 10}, {'name': 'beta*', 'count': 20}], [{'name': 'delta*', 'count': 40}, {'name': 'epsilon*', 'count': 50}], [{'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n", + "Copy serializer: msgpack\n", + "Copy codec: Codec.LZ4\n" + ] + } + ], + "source": [ + "store_copy = store.copy(\n", + " urlpath=copy_path,\n", + " contiguous=False,\n", + " cparams={\"codec\": blosc2.Codec.LZ4, \"clevel\": 5},\n", + ")\n", + "\n", + "show(\"Copied batches\", [batch[:] for batch in store_copy])\n", + "show(\"Copy serializer\", store_copy.serializer)\n", + "show(\"Copy codec\", store_copy.cparams.codec)" + ] + }, + { + "cell_type": "markdown", + "id": "19c51a629db1209", + "metadata": {}, + "source": [ + "## Round-tripping through cframes and reopening from disk\n", + "\n", + "Tagged persistent stores automatically reopen as `BatchStore`, and a serialized cframe buffer does too." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "fd4957093f509bd4", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.359063Z", + "start_time": "2026-03-20T10:24:10.343012Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.591475Z", + "iopub.status.busy": "2026-03-20T10:23:51.591415Z", + "iopub.status.idle": "2026-03-20T10:23:51.594839Z", + "shell.execute_reply": "2026-03-20T10:23:51.594553Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "from_cframe type: BatchStore\n", + "from_cframe batches: [[{'name': 'alpha*', 'count': 10}, {'name': 'beta*', 'count': 20}], [{'name': 'delta*', 'count': 40}, {'name': 'epsilon*', 'count': 50}], [{'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n", + "Reopened type: BatchStore\n", + "Reopened batches: [[{'name': 'alpha*', 'count': 10}, {'name': 'beta*', 'count': 20}], [{'name': 'delta*', 'count': 40}, {'name': 'epsilon*', 'count': 50}], [{'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}], [{'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}], [{'name': 'iota', 'count': 9}, {'name': 'kappa', 'count': 10}, {'name': 'lambda', 'count': 11}]]\n" + ] + } + ], + "source": [ + "cframe = store.to_cframe()\n", + "restored = blosc2.from_cframe(cframe)\n", + "show(\"from_cframe type\", type(restored).__name__)\n", + "show(\"from_cframe batches\", [batch[:] for batch in restored])\n", + "\n", + "reopened = blosc2.open(urlpath, mode=\"r\", mmap_mode=\"r\")\n", + "show(\"Reopened type\", type(reopened).__name__)\n", + "show(\"Reopened batches\", [batch[:] for batch in reopened])" + ] + }, + { + "cell_type": "markdown", + "id": "dc362a1cab78d016", + "metadata": {}, + "source": [ + "## Clearing and reusing a store\n", + "\n", + "Calling `clear()` resets the backing storage so the container remains ready for new batches." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2214b2be1bfb5bc7", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.386442Z", + "start_time": "2026-03-20T10:24:10.365740Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.595854Z", + "iopub.status.busy": "2026-03-20T10:23:51.595778Z", + "iopub.status.idle": "2026-03-20T10:23:51.601478Z", + "shell.execute_reply": "2026-03-20T10:23:51.601232Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After clear + extend: [[{'name': 'fresh', 'count': 1}], [{'name': 'again', 'count': 2}, {'name': 'done', 'count': 3}]]\n" + ] + } + ], + "source": [ + "scratch = store.copy()\n", + "scratch.clear()\n", + "scratch.extend(\n", + " [\n", + " [{\"name\": \"fresh\", \"count\": 1}],\n", + " [{\"name\": \"again\", \"count\": 2}, {\"name\": \"done\", \"count\": 3}],\n", + " ]\n", + ")\n", + "show(\"After clear + extend\", [batch[:] for batch in scratch])" + ] + }, + { + "cell_type": "markdown", + "id": "8d8f9df58a46c4c1", + "metadata": {}, + "source": [ + "## Flat item access with `.items`\n", + "\n", + "The main `BatchStore` API remains batch-oriented, but the `.items` accessor offers a read-only flat view across all items. Integer indexing returns one item and slicing returns a Python list." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4f5c4e5a1b8f92d4", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.403443Z", + "start_time": "2026-03-20T10:24:10.387808Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.602502Z", + "iopub.status.busy": "2026-03-20T10:23:51.602451Z", + "iopub.status.idle": "2026-03-20T10:23:51.606267Z", + "shell.execute_reply": "2026-03-20T10:23:51.605893Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Flat item 0: {'name': 'alpha*', 'count': 10}\n", + "Flat item 6: {'name': 'eta', 'count': 7}\n", + "Flat slice 3:8: [{'name': 'epsilon*', 'count': 50}, {'name': 'between-a', 'count': 99}, {'name': 'between-b', 'count': 100}, {'name': 'eta', 'count': 7}, {'name': 'theta', 'count': 8}]\n" + ] + } + ], + "source": [ + "show(\"Flat item 0\", store.items[0])\n", + "show(\"Flat item 6\", store.items[6])\n", + "show(\"Flat slice 3:8\", store.items[3:8])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "2a355a3fc8673692", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-20T10:24:10.420064Z", + "start_time": "2026-03-20T10:24:10.403926Z" + }, + "execution": { + "iopub.execute_input": "2026-03-20T10:23:51.607247Z", + "iopub.status.busy": "2026-03-20T10:23:51.607185Z", + "iopub.status.idle": "2026-03-20T10:23:51.608877Z", + "shell.execute_reply": "2026-03-20T10:23:51.608598Z" + } + }, + "outputs": [], + "source": [ + "blosc2.remove_urlpath(urlpath)\n", + "blosc2.remove_urlpath(copy_path)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/reference/batch_store.rst b/doc/reference/batch_store.rst new file mode 100644 index 00000000..6b1edc8d --- /dev/null +++ b/doc/reference/batch_store.rst @@ -0,0 +1,93 @@ +.. _BatchStore: + +BatchStore +========== + +Overview +-------- +BatchStore is a batch-oriented container for variable-length Python items +backed by a single Blosc2 ``SChunk``. + +Each batch is stored in one compressed chunk: + +- batches contain one or more Python items +- each chunk may contain one or more internal variable-length blocks +- the store itself is indexed by batch +- item-wise traversal is available via :meth:`BatchStore.iter_items` + +BatchStore is a good fit when data arrives naturally in batches and you want: + +- efficient batch append/update operations +- persistent ``.b2b`` stores +- item-level reads inside a batch +- compact summary information about batches and internal blocks via ``.info`` + +Serializer support +------------------ + +BatchStore currently supports two serializers: + +- ``"msgpack"``: the default and general-purpose choice for Python items +- ``"arrow"``: optional and requires ``pyarrow``; mainly useful when data is + already Arrow-shaped before ingestion + +Quick example +------------- + +.. code-block:: python + + import blosc2 + + store = blosc2.BatchStore(urlpath="example_batch_store.b2b", mode="w", contiguous=True) + store.append([{"red": 1, "green": 2, "blue": 3}, {"red": 4, "green": 5, "blue": 6}]) + store.append([{"red": 7, "green": 8, "blue": 9}]) + + print(store[0]) # first batch + print(store[0][1]) # second item in first batch + print(list(store.iter_items())) + + reopened = blosc2.open("example_batch_store.b2b", mode="r") + print(type(reopened).__name__) + print(reopened.info) + +.. note:: + BatchStore is batch-oriented by design. ``store[i]`` returns a batch, not a + single item. Use :meth:`BatchStore.iter_items` for flat item-wise traversal. + +.. currentmodule:: blosc2 + +.. autoclass:: BatchStore + + Constructors + ------------ + .. automethod:: __init__ + + Batch Interface + --------------- + .. automethod:: __getitem__ + .. automethod:: __setitem__ + .. automethod:: __delitem__ + .. automethod:: __len__ + .. automethod:: __iter__ + .. automethod:: iter_items + + Mutation + -------- + .. automethod:: append + .. automethod:: extend + .. automethod:: insert + .. automethod:: pop + .. automethod:: delete + .. automethod:: clear + .. automethod:: copy + + Context Manager + --------------- + .. automethod:: __enter__ + .. automethod:: __exit__ + + Public Members + -------------- + .. automethod:: to_cframe + +.. autoclass:: Batch diff --git a/doc/reference/classes.rst b/doc/reference/classes.rst index cca8c7e0..83733b2f 100644 --- a/doc/reference/classes.rst +++ b/doc/reference/classes.rst @@ -16,6 +16,8 @@ Main Classes DictStore TreeStore EmbedStore + BatchStore + VLArray Proxy ProxySource ProxyNDSource @@ -33,6 +35,8 @@ Main Classes dict_store tree_store embed_store + batch_store + vlarray proxy proxysource proxyndsource diff --git a/doc/reference/misc.rst b/doc/reference/misc.rst index 50cb0c1b..b6e0ddee 100644 --- a/doc/reference/misc.rst +++ b/doc/reference/misc.rst @@ -134,6 +134,8 @@ This page documents the miscellaneous members of the ``blosc2`` module that do n TreeStore, DictStore, EmbedStore, + VLArray, + vlarray_from_cframe, abs, acos, acosh, diff --git a/examples/batch_store.py b/examples/batch_store.py new file mode 100644 index 00000000..9a809fec --- /dev/null +++ b/examples/batch_store.py @@ -0,0 +1,73 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import random + +import blosc2 + +URLPATH = "example_batch_store.b2b" +NBATCHES = 100 +OBJECTS_PER_BATCH = 100 +BLOCKSIZE_MAX = 32 +N_RANDOM_SAMPLES = 5 + + +def make_rgb(batch_index: int, item_index: int) -> dict[str, int]: + global_index = batch_index * OBJECTS_PER_BATCH + item_index + return { + "red": batch_index, + "green": item_index, + "blue": global_index, + } + + +def make_batch(batch_index: int) -> list[dict[str, int]]: + return [make_rgb(batch_index, item_index) for item_index in range(OBJECTS_PER_BATCH)] + + +def main() -> None: + # Start clean so the example is reproducible when run multiple times. + blosc2.remove_urlpath(URLPATH) + + storage = blosc2.Storage(urlpath=URLPATH, mode="w", contiguous=True) + with blosc2.BatchStore(storage=storage, max_blocksize=BLOCKSIZE_MAX) as store: + for batch_index in range(NBATCHES): + store.append(make_batch(batch_index)) + + total_objects = sum(len(batch) for batch in store) + print("Created BatchStore") + print(f" batches: {len(store)}") + print(f" objects: {total_objects}") + print(f" max_blocksize: {store.max_blocksize}") + + # Reopen with the same max_blocksize hint so scalar reads can use the + # VL-block path instead of decoding the entire batch. + reopened = blosc2.BatchStore(urlpath=URLPATH, mode="r", contiguous=True, max_blocksize=BLOCKSIZE_MAX) + + print() + print(reopened.info) + + sample_rng = random.Random(2024) + print("Random scalar reads:") + for _ in range(N_RANDOM_SAMPLES): + batch_index = sample_rng.randrange(len(reopened)) + item_index = sample_rng.randrange(OBJECTS_PER_BATCH) + value = reopened[batch_index][item_index] + print(f" reopened[{batch_index}][{item_index}] -> {value}") + + print() + print("Flat item reads via .items:") + print(f" reopened.items[0] -> {reopened.items[0]}") + print(f" reopened.items[150:153] -> {reopened.items[150:153]}") + + print(f"BatchStore file at: {reopened.urlpath}") + + +if __name__ == "__main__": + main() diff --git a/examples/ndarray/mandelbrot-pure-dsl.ipynb b/examples/ndarray/mandelbrot-pure-dsl.ipynb index 68c1f505..e29ba7cd 100644 --- a/examples/ndarray/mandelbrot-pure-dsl.ipynb +++ b/examples/ndarray/mandelbrot-pure-dsl.ipynb @@ -4,18 +4,20 @@ "cell_type": "markdown", "id": "intro", "metadata": {}, - "source": "# Mandelbrot With Blosc2 DSL\n\nThis notebook shows how Blosc2 DSL can be used to accelerate the computation of the Mandelbrot set.\n- `@blosc2.dsl_kernel` through `blosc2.lazyudf` (`blosc2+DSL`)\n- a NumPy-based implementation for reference.\n\nThis can run in the three major platforms (Linux, Windows, MacOS) and on browsers via Web Assembly (WASM).\n" + "source": "# Mandelbrot With Blosc2 DSL\n\nThis notebook shows how Blosc2 DSL can be used to accelerate the computation of the Mandelbrot set.\n- `@blosc2.dsl_kernel` through `blosc2.lazyudf` (`blosc2+DSL`)\n- a pure Python `blosc2.lazyudf` using NumPy operations on each block (`blosc2+NumPy`).\n\nThis can run in the three major platforms (Linux, Windows, MacOS) and on browsers via Web Assembly (WASM).\n" }, { "cell_type": "code", + "execution_count": 1, "id": "imports", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:17:58.314706Z", - "start_time": "2026-03-04T09:17:58.035312Z" - } + "end_time": "2026-03-09T11:41:27.988249Z", + "start_time": "2026-03-09T11:41:27.734339Z" + }, + "trusted": true }, + "outputs": [], "source": [ "import time\n", "\n", @@ -23,20 +25,28 @@ "import numpy as np\n", "\n", "import blosc2" - ], - "outputs": [], - "execution_count": 1 + ] }, { "cell_type": "code", + "execution_count": 2, "id": "grid-setup", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:17:58.367732Z", - "start_time": "2026-03-04T09:17:58.326450Z" - } + "end_time": "2026-03-09T11:41:28.008743Z", + "start_time": "2026-03-09T11:41:27.994350Z" + }, + "trusted": true }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "grid: (800, 1200), dtype: float32\n" + ] + } + ], "source": [ "# Problem size and Mandelbrot domain\n", "WIDTH = 1200\n", @@ -52,32 +62,25 @@ "\n", "# Keep compression overhead low for the timing comparison\n", "cparams_fast = blosc2.CParams(codec=blosc2.Codec.LZ4, clevel=1)\n", + "cparams_single_thread = blosc2.CParams(codec=blosc2.Codec.LZ4, clevel=1, nthreads=1)\n", "cr_b2 = blosc2.asarray(cr_np, cparams=cparams_fast)\n", "ci_b2 = blosc2.asarray(ci_np, cparams=cparams_fast)\n", "\n", "print(f\"grid: {cr_np.shape}, dtype: {cr_np.dtype}\")" - ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "grid: (800, 1200), dtype: float32\n" - ] - } - ], - "execution_count": 2 + ] }, { "cell_type": "code", + "execution_count": 3, "id": "dsl-kernel", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:17:58.385387Z", - "start_time": "2026-03-04T09:17:58.368666Z" - } + "end_time": "2026-03-09T11:41:28.027809Z", + "start_time": "2026-03-09T11:41:28.014980Z" + }, + "trusted": true }, + "outputs": [], "source": [ "@blosc2.dsl_kernel\n", "def mandelbrot_dsl(cr, ci, max_iter):\n", @@ -92,26 +95,27 @@ " zi = 2 * zr * zi + ci\n", " zr = zr_new\n", " return escape_iter" - ], - "outputs": [], - "execution_count": 3 + ] }, { "cell_type": "code", + "execution_count": 4, "id": "d166bd73f67513f9", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:17:58.405886Z", - "start_time": "2026-03-04T09:17:58.388118Z" - } + "end_time": "2026-03-09T11:41:28.043053Z", + "start_time": "2026-03-09T11:41:28.028995Z" + }, + "trusted": true }, + "outputs": [], "source": [ - "def mandelbrot_numpy(cr, ci, max_iter):\n", - " zr = np.zeros_like(cr, dtype=np.float32)\n", - " zi = np.zeros_like(ci, dtype=np.float32)\n", - " out = np.full(cr.shape, np.float32(max_iter), dtype=np.float32)\n", - " active = np.ones(cr.shape, dtype=bool)\n", + "def mandelbrot_numpy_udf(inputs_tuple, output, offset):\n", + " cr, ci, max_iter = inputs_tuple\n", + " zr = np.zeros_like(output, dtype=np.float32)\n", + " zi = np.zeros_like(output, dtype=np.float32)\n", + " output[:] = np.float32(max_iter)\n", + " active = np.ones(output.shape, dtype=bool)\n", "\n", " for it in range(max_iter):\n", " if not active.any():\n", @@ -128,7 +132,7 @@ " escaped = zr2 + zi2 > np.float32(4.0)\n", "\n", " if escaped.any():\n", - " out[iy[escaped], ix[escaped]] = np.float32(it)\n", + " output[iy[escaped], ix[escaped]] = np.float32(it)\n", " active[iy[escaped], ix[escaped]] = False\n", "\n", " keep = ~escaped\n", @@ -140,11 +144,8 @@ " cr_s = cr_a[keep]\n", " ci_s = ci_a[keep]\n", " zr[iy[keep], ix[keep]] = zr_s * zr_s - zi_s * zi_s + cr_s\n", - " zi[iy[keep], ix[keep]] = np.float32(2.0) * zr_s * zi_s + ci_s\n", - " return out" - ], - "outputs": [], - "execution_count": 4 + " zi[iy[keep], ix[keep]] = np.float32(2.0) * zr_s * zi_s + ci_s" + ] }, { "cell_type": "markdown", @@ -154,14 +155,46 @@ }, { "cell_type": "code", + "execution_count": 5, "id": "benchmark", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:18:07.215852Z", - "start_time": "2026-03-04T09:17:58.406755Z" - } + "end_time": "2026-03-09T11:41:38.137557Z", + "start_time": "2026-03-09T11:41:28.044354Z" + }, + "trusted": true }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First iteration timings (one-time overhead included):\n", + "Blosc2+NumPy first run: 1.529686 s\n", + "Blosc2+DSL (no JIT) first run: 0.208474 s\n", + "Blosc2+DSL (1 thread) first run: 0.268345 s\n", + "Blosc2+DSL first run: 0.036961 s\n", + "\n", + "Best-time stats:\n", + "Blosc2+NumPy time (best): 1.496923 s\n", + "Blosc2+DSL (no JIT) time (best): 0.194362 s\n", + "Blosc2+DSL (1 thread) time (best): 0.267587 s\n", + "Blosc2+DSL time (best): 0.033397 s\n", + "Blosc2+NumPy / Blosc2+DSL (no JIT): 7.70x\n", + "Blosc2+NumPy / Blosc2+DSL (1 thread): 5.59x\n", + "Blosc2+NumPy / Blosc2+DSL: 44.82x\n", + "\n", + "Cold-start overhead (first - best):\n", + "Blosc2+NumPy overhead: 0.032763 s\n", + "Blosc2+DSL (no JIT) overhead: 0.014112 s\n", + "Blosc2+DSL (1 thread) overhead: 0.000758 s\n", + "Blosc2+DSL overhead: 0.003564 s\n", + "max |blosc2+numpy-dsl(no jit)|: 0.000000\n", + "max |blosc2+numpy-dsl(1 thread)|: 0.000000\n", + "max |blosc2+numpy-dsl|: 0.000000\n" + ] + } + ], "source": [ "def best_time(func, repeats=3, warmup=1):\n", " for _ in range(warmup):\n", @@ -178,8 +211,35 @@ " return best, best_out\n", "\n", "\n", - "def run_numpy():\n", - " return mandelbrot_numpy(cr_np, ci_np, MAX_ITER)\n", + "def run_numpy_udf():\n", + " lazy = blosc2.lazyudf(\n", + " mandelbrot_numpy_udf,\n", + " (cr_b2, ci_b2, MAX_ITER),\n", + " dtype=np.float32,\n", + " cparams=cparams_fast,\n", + " )\n", + " return lazy.compute()\n", + "\n", + "\n", + "def run_dsl_no_jit():\n", + " lazy = blosc2.lazyudf(\n", + " mandelbrot_dsl,\n", + " (cr_b2, ci_b2, MAX_ITER),\n", + " dtype=np.float32,\n", + " cparams=cparams_fast,\n", + " jit=False,\n", + " )\n", + " return lazy.compute()\n", + "\n", + "\n", + "def run_dsl_single_thread():\n", + " lazy = blosc2.lazyudf(\n", + " mandelbrot_dsl,\n", + " (cr_b2, ci_b2, MAX_ITER),\n", + " dtype=np.float32,\n", + " cparams=cparams_single_thread,\n", + " )\n", + " return lazy.compute()\n", "\n", "\n", "def run_dsl():\n", @@ -192,10 +252,20 @@ " return lazy.compute()\n", "\n", "\n", - "# Measure first iteration (includes one-time overhead, especially JIT compile)\n", + "# Measure first iteration (includes one-time overhead, especially DSL JIT compile)\n", "t0 = time.perf_counter()\n", - "_ = run_numpy()\n", - "t_numpy_first = time.perf_counter() - t0\n", + "_ = run_numpy_udf()\n", + "t_numpy_udf_first = time.perf_counter() - t0\n", + "\n", + "\n", + "t0 = time.perf_counter()\n", + "_ = run_dsl_no_jit()\n", + "t_dsl_no_jit_first = time.perf_counter() - t0\n", + "\n", + "\n", + "t0 = time.perf_counter()\n", + "_ = run_dsl_single_thread()\n", + "t_dsl_single_thread_first = time.perf_counter() - t0\n", "\n", "\n", "t0 = time.perf_counter()\n", @@ -203,10 +273,14 @@ "t_dsl_first = time.perf_counter() - t0\n", "\n", "\n", - "t_numpy, img_numpy = best_time(run_numpy)\n", + "t_numpy_udf, img_numpy_udf = best_time(run_numpy_udf)\n", + "t_dsl_no_jit, img_dsl_no_jit = best_time(run_dsl_no_jit)\n", + "t_dsl_single_thread, img_dsl_single_thread = best_time(run_dsl_single_thread)\n", "t_dsl, img_dsl = best_time(run_dsl)\n", "\n", - "cold_overhead_native = t_numpy_first - t_numpy\n", + "cold_overhead_numpy_udf = t_numpy_udf_first - t_numpy_udf\n", + "cold_overhead_dsl_no_jit = t_dsl_no_jit_first - t_dsl_no_jit\n", + "cold_overhead_dsl_single_thread = t_dsl_single_thread_first - t_dsl_single_thread\n", "cold_overhead_dsl = t_dsl_first - t_dsl\n", "\n", "\n", @@ -214,60 +288,66 @@ " return np.max(np.abs(np.asarray(a) - np.asarray(b))).item()\n", "\n", "\n", - "c_max = _max_abs_diff(img_numpy, img_dsl)\n", + "b_max = _max_abs_diff(img_numpy_udf, img_dsl_no_jit)\n", + "c_max = _max_abs_diff(img_numpy_udf, img_dsl_single_thread)\n", + "d_max = _max_abs_diff(img_numpy_udf, img_dsl)\n", "\n", "print(\"First iteration timings (one-time overhead included):\")\n", - "print(f\"NumPy first run (baseline): {t_numpy_first:.6f} s\")\n", - "print(f\"Blosc2+DSL first run: {t_dsl_first:.6f} s\")\n", + "print(f\"Blosc2+NumPy first run: {t_numpy_udf_first:.6f} s\")\n", + "print(f\"Blosc2+DSL (no JIT) first run: {t_dsl_no_jit_first:.6f} s\")\n", + "print(f\"Blosc2+DSL (1 thread) first run: {t_dsl_single_thread_first:.6f} s\")\n", + "print(f\"Blosc2+DSL first run: {t_dsl_first:.6f} s\")\n", "\n", "print(\"\\nBest-time stats:\")\n", - "print(f\"NumPy time (best): {t_numpy:.6f} s\")\n", + "print(f\"Blosc2+NumPy time (best): {t_numpy_udf:.6f} s\")\n", + "print(f\"Blosc2+DSL (no JIT) time (best): {t_dsl_no_jit:.6f} s\")\n", + "print(f\"Blosc2+DSL (1 thread) time (best): {t_dsl_single_thread:.6f} s\")\n", "print(f\"Blosc2+DSL time (best): {t_dsl:.6f} s\")\n", - "print(f\"NumPy / Blosc2+DSL: {t_numpy / t_dsl:.2f}x\")\n", + "print(f\"Blosc2+NumPy / Blosc2+DSL (no JIT): {t_numpy_udf / t_dsl_no_jit:.2f}x\")\n", + "print(f\"Blosc2+NumPy / Blosc2+DSL (1 thread): {t_numpy_udf / t_dsl_single_thread:.2f}x\")\n", + "print(f\"Blosc2+NumPy / Blosc2+DSL: {t_numpy_udf / t_dsl:.2f}x\")\n", "print(\"\\nCold-start overhead (first - best):\")\n", - "print(f\"NumPy overhead: {cold_overhead_native:.6f} s\")\n", - "print(f\"Blosc2+DSL overhead: {cold_overhead_dsl:.6f} s\")\n", - "\n", - "print(f\"max |numpy-dsl(cc)|: {c_max:.6f}\")" - ], - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "First iteration timings (one-time overhead included):\n", - "NumPy first run (baseline): 1.797350 s\n", - "Blosc2+DSL first run: 0.057364 s\n", - "\n", - "Best-time stats:\n", - "NumPy time (best): 1.654180 s\n", - "Blosc2+DSL time (best): 0.034381 s\n", - "NumPy / Blosc2+DSL: 48.11x\n", - "\n", - "Cold-start overhead (first - best):\n", - "NumPy overhead: 0.143169 s\n", - "Blosc2+DSL overhead: 0.022983 s\n", - "max |numpy-dsl(cc)|: 0.000000\n" - ] - } - ], - "execution_count": 5 + "print(f\"Blosc2+NumPy overhead: {cold_overhead_numpy_udf:.6f} s\")\n", + "print(f\"Blosc2+DSL (no JIT) overhead: {cold_overhead_dsl_no_jit:.6f} s\")\n", + "print(f\"Blosc2+DSL (1 thread) overhead: {cold_overhead_dsl_single_thread:.6f} s\")\n", + "print(f\"Blosc2+DSL overhead: {cold_overhead_dsl:.6f} s\")\n", + "\n", + "print(f\"max |blosc2+numpy-dsl(no jit)|: {b_max:.6f}\")\n", + "print(f\"max |blosc2+numpy-dsl(1 thread)|: {c_max:.6f}\")\n", + "print(f\"max |blosc2+numpy-dsl|: {d_max:.6f}\")" + ] }, { "cell_type": "code", + "execution_count": 6, "id": "plot", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:18:07.479845Z", - "start_time": "2026-03-04T09:18:07.233302Z" - } + "end_time": "2026-03-09T11:41:38.385197Z", + "start_time": "2026-03-09T11:41:38.153710Z" + }, + "trusted": true }, + "outputs": [ + { + "data": { + "image/png": "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lpYWUaj7mWeeyfi5oihC2qlramoSKR60fWgbkJhPBt1wo+1/+PBhIa60Hu9617vE9qZ1tH8A0LqedNJJQuDf+9734sc//rEQ4qqqquS0aH//8pe/FOJHf9O0SRzPPvvsSZeBlpmgHxC5giT9G9/4Bl73uteJpx5zCW2DlStXivWj/U3bi+osUk0e2hd0DFL6ER3Pb33rW6ecHm1DklT68UL84he/EOk443+IHHfccVi9erXYxqk8/fTTYvzxwzMMwzBMocOeyZ5ZjJ6ZDToO6Ybyeeedl+xHT5JSWZxcetpLL70k5pOtPirDMIUNNVvGHXfcAeYVV1xhEps3bzavueYac2hoyHS73eKz3/3ud+bf//538ffevXvNP//5z2nj2sPZnaqq5nPPPWfed999af2JSCRiLlu2LNlv/fr1ov973/veZL8//vGPZigUMtva2pL91qxZY8bjcTGs3a+9vV30u/baa9Pms27dOjMWi6X1v+mmm8Sy2+8XL14sphUMBs2WlpZk/+OPP170/9///d+0cYmvfOUrafN53eteJ/p/+tOfTut/yy23mLqup63nyMiImM509sXZZ58tpnvBBRdM+Oz+++83M/Hiiy+aS5YsybhPaV3pfV1dndj+d911lylJUnI42t/ElVdeKd5v2LBBvL/sssuyLuPy5ctNTdPMW2+9NW1a0+nuuecec3Bw0KysrJx0uM9//vNp+yxTd8YZZySXNRAImH19feZtt92Wtu9o248/Dmna46dF80rdR/b2u/POO9OGe/jhh8X+/f73v5/sJ8uyeeDAAbF/xs/rxhtvNGtra8X2p+Pr3nvvFf0//OEPi2Fouel4v/7669PG/eY3vymW3ev1pvU/6aSTxPhvfOMbZ/Vd54477rjjjrv57tgzrf7smcXrmdmGefbZZ4V/2u9f9apXieOGOnLGr371q+a5554rjtvx42Y63rN127dvN//617/m7DvJHXfcYV46fuKRYSapI0LFoy+88ELxWD+9Zkt/IVILgVOkkiLAVAslUyvDVBeGWsezoVQYSkWgKCchy7JIibnttttEVNOGUksoOprK61//ejE8La8dmaWO0kEoKjudBjhoPhSVtXnyySdFvZtMUfQf/OAHae9pGIq6f/vb307rT3VfaLkogj8baB0ISuvNBKUEv+pVrxIdNVBCTwLQNqdC2pPVRqLh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", + "text/plain": [ + "
" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig, ax = plt.subplots(1, 2, figsize=(13, 5), constrained_layout=True)\n", "\n", "im0 = ax[0].imshow(\n", - " img_numpy,\n", + " img_numpy_udf,\n", " cmap=\"magma\",\n", " extent=(X_MIN, X_MAX, Y_MIN, Y_MAX),\n", " origin=\"lower\",\n", @@ -289,97 +369,77 @@ "fig.colorbar(im1, ax=ax[1], shrink=0.82, label=\"Escape iteration\")\n", "\n", "plt.show()" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "timing-bars", + "metadata": { + "ExecuteTime": { + "end_time": "2026-03-09T11:41:38.459742Z", + "start_time": "2026-03-09T11:41:38.393777Z" + }, + "trusted": true + }, "outputs": [ { "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABFcAAAH/CAYAAACSKTLZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAbSRJREFUeJzt3Qm8jPX////XsdexlCWkSIQWSagUKUpSn0RFixLFx9InosVSiYpEJGkjS30QLdqzlahI2SWqT4TsS/ad+d+e7+9/5jczZ845c84158xZHvfb7box11wz855rO3O9rtf79U4wM58BAAAAAAAgXfKk72UAAAAAAAAQgisAAAAAAAAeEFwBAAAAAADwgOAKAAAAAACABwRXAAAAAAAAPCC4AgAAAAAA4AHBFQAAAAAAAA8IrgAAAAAAAHhAcAUAAAAAAMADgisAkI317dvXfD5ful47duxYW7t2beBxhQoV3Hv16NHDsqs2bdq476DvkhXWcXYxe/ZsNwGZRecenYOQc+g8OWLECMsK/H/P9DcBADILwRUASOVCXdNVV10VcZn169e75z/77LNMb19216tXL2vWrFmqy+mi378dUpoUBEHohUXwtGfPHluyZIl16dLF8uTJWn/+zzrrLHv66adtwYIFtmvXLtu+fbvb7o0aNYrZZ+hCXuth2bJlWe7CMPhco+nQoUP222+/ufacccYZlh00aNAgyT63c+dOmz9/vt19992W1VStWtUGDRrkjom9e/fapk2b7PPPP7datWqle5tt3LjRpk2bZv/5z3+scOHCEV+nvyVffvml/f333+4169ats08//dTuuuuumOyP/mCwfzpx4oT7bvobdfnll6f5/QAA0cuXhmUBIFfSD2BdHPzwww9JLibOPvtsO3z4cNzalp317t3bPvjgA/vkk09SXO7555+30aNHBx7XqVPHunbt6uavWrUqMH/58uW2cuVKe++99+zIkSMxa+dzzz1nL7zwgmVHEydOdBdyUqxYMWvatKm9+uqrLvjy+OOPW1ahINsTTzxhH3/8sY0fP97y5ctn9913n82aNcvatm1r48aNi9lnXXzxxdaiRQv76KOPLKt56qmnXEZHoUKFrF69etapUye3zS666CJ3HsoOhg8fbj///LP7f4kSJaxVq1Y2YcIEO+200+y1116zrOLBBx+0Bx54wD788EPXLh0f//73v+3HH3+0Jk2a2Ndff52mbZY/f34rU6aMXXPNNfbyyy9b9+7d7ZZbbrEVK1YElr399ttt8uTJtnTpUree/vnnH6tYsaJdffXV1r59e5s0aVLMvl/Hjh1t//79LpCqv1N6/7lz59pll12WbIARAOCdcp2ZmJiYmMKmNm3a+OSDDz7wbdu2zZc3b96Q5998803fzz//7Fu7dq3vs88+i0sb+/bt69qYnteOHTvWtd3/uEKFCu69evToka73O/XUU9O0/L59+1wb0vo5t912m2tngwYN4r6PZNUppW25YMEC399//x0yb/bs2W6KV3svuOACX4kSJULmFShQwPfrr7/61q9fn+rrtR/rWEhpGe1rBw4c8K1evdq3dOnSJM/LiBEj4nquqVWrVsj8IUOGuPl33nln3Pep1CYdj6LjM3h+/vz5fRs2bPB9//33SbZZeo7/WE2XXnqpLzExMWRe8eLFfVu3bvV999136d5mmq699lq3r+k7FipUKDD/l19+8a1YscKtk/DXlCpVKib7o/9vQvjxpGNMnnvuuQxbp/E8hpI7B2o7xbstTExMlmumrJUXDABZkO4m6g7s9ddfH5inu5S6C6nMgEhUt0SZLjt27LCDBw/awoUL7bbbbkuynD/1W3fudYdTWTC//PKL3XDDDRHTyX/66Sd3B/t///ufdejQIdk233PPPe4z9dlKzdd3UNeLaHXr1s3++usv9/pvv/3WLrzwwiRdLPbt22fnnnuuffHFFy6tXnen5dRTT7UhQ4a4LlP6PqtXr05Sx0XfW2nz999/fyB9PRb1FyLVXNFdZaXEK9NId9T1nZTlosfSvHlz91jrVevskksuSbXmSlq2m/9zg7dbpPe87rrr7LvvvnN3s7Vutd6UnRNMd6DVncGLrVu32vHjx1NdrlSpUi5jaMuWLa7tutuubJJwykzQetM+oK5HWpcPP/xwyDLKChg6dKjbFlpXGzZscBkqOq7k119/dftpsKNHj7qsG33n5LpYpNXJkyddJlKNGjXcdk9P/R5/9xf//iPqwqT9oHr16u54OXDggP3xxx+BY16ZCcqI0L6n7Rptd6dvvvnG/avsBk36XB2b4erWreueu/POOyO+j7oWHTt2zHW9ClelShX3WnUXE2UNabnff//dbXedw7Rfav9MD32u9ulo9jl9xylTprh9QetQXYqU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r149F0hp3ry5lSxZ0l577TUrUaKEtWvXLuJrBg4caEOHDo24YgiuAAAAAAAAr7JVcEW1VVT49p577rG9e/e6ed27d7cPPvjAOnfuHDF75ejRo24CAAAAAACw3D4U8+bNm13WiT+wIqtWrXJBl7POOiuubQMAAAAAALlTtgqu/PDDD26kH40y5FelShU7ceKE/f3333FtGwAAAAAAyJ3iPhRz5cqVA48rVqxoNWrUsF27dtmGDRtswIABrvBsmzZt3PMTJ050xWvHjh1rffv2dTVXBg8e7IZyTq6gLQAAAAAAmS0hIcFOO+00V/dT/0fWo7IjqsW6e/du9/9sG1ypXbu2ffvtt4HHw4YNc/+OGzfO2rZt64ZNLl++fOB5jQ50/fXX24gRI9yoQTt37rQpU6bYk08+GZf2AwAAAAAQrlSpUta+fXurVq1avJuCKKxevdpGjRpl27dvt/RS+MxbeCabUdRQNVuKFi3KaEEAAAAAgJjKly+fG9V2//79Lhlg27ZtrpQFsp68efPaGWecYS1btrTChQu7gXKOHz+e7hiCLzdNRYoU8Yn+jXdbsspUv35936effurbuHGjWzfNmjVLcfkGDRr4IildunRgmY4dO/qWLVvm27Nnj5vmzZvna9KkSdy/KxMTExMTExMTExMTU0ZOZ599tu+dd97xValSJe5tYbKoJm0rbbOzzjor3TGEbFXQFhlX+2bZsmXWpUuXNL1OxYTLlCkTmBSR9VOB4Z49e1qtWrVc969vvvnGPvnkE7vgggsy4BsAAAAAQNag0WzlyJEj8W4KouTfVspkyZY1V5A1TJs2zU1ppWDKnj17Ij73+eefhzxWXZxOnTrZFVdcYb/++qubp6LE7dq1s9KlS7v6OR988IF17do1nd8CAAAAAID4IHMF6bZ06VLbtGmTzZgxw6688soUI7etWrVyGTLz589382677TZ75JFH7N///redd955duutt9qKFSsysfUAAAAAAMQGmStIs82bN7ugiEZsKliwoD344INu1KfLL7/clixZEljuoosucsGUQoUKuWJOzZs3t1WrVrnnNArUli1bbNasWa5gkIbe/vnnn+P4rQAAAAAgY93c871M/bzPX7gzZu81e/Zsd4NdN8mRFJkrSLPff//d3nrrLVu8eLELnjzwwAM2b968JAfZb7/9ZpdccokLurz++us2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W+b7779379GmTRt75pln7JJLLglkx2heclk0U6dOtd69e9vGjRvtt99+c/P1mmbNmoUsq8/wv4/aoWWaN29u33zzjR04cMCWLl1qV1xxhWUkgisAAAAAACCJxMREa926tf3xxx8uiCL58uWz6dOn2759+6x+/fp21VVXuSDKtGnTXHZJ3rx5XbBlzpw5dvHFF1vdunXtrbfecgEPBWSGDBliv/zySyA7RvOSo8yZqlWr2vXXX28333xzmtr+/PPPu89SIOf333+3SZMmubZlFAraAgAAAAAAR0EMBU6kcOHCtmnTJjdPwRFp1aqV5cmTxx588MHAa9q2bWu7d++2a665xhYuXGinnXaa60q0Zs0a9/zq1asDyyoQo65GyoxJjbJO9DnHjh1L8/dQYOXLL78M1Hn59ddfrXLlyoEMmFgjcwUAAAAAADizZ8922R6a6tSp47JUVIdF3XqkRo0aLkihAIx/2rVrlxUqVMgqVarkuuioS49e9+mnn9rDDz/sMlTSY8WKFekKrMjy5csD/9+8ebP794wzzrCMQnAFAAAAAAAEskX+/PNPNykLRZkj6h7Uvn37QDbLokWLAgEY/1SlShWbOHGiW6Zdu3auO9C8efNcpou65Vx++eXpaku4kydPWkJCQqrFboODMv6sG2XcZBS6BQEAAAAAgIgUmFBA45RTTnGPFy9e7AIm27ZtC3QfikRFZDWpIK6CLHfffbcrcnv06FFPtU+2b99uZcuWDTxWFo2CP/FG5goAAAAAAHAKFixopUuXdlO1atVsxIgRLlvls88+c89PmDDBduzYYZ988onVq1fPzjnnHDc60PDhw61cuXLu8YABA9zoPOpKpGK05513nq1atcq9/q+//rKKFSu67kUlSpSwAgUKpKl9GgHooYcectkytWrVsjfeeMMFbOKNzBUAAAAAADJBYp8tltXdeOONtmXL/7Vz7969rhjtHXfc4Ub/kUOHDtnVV19tgwYNso8++siKFCnihkr++uuv3fLKcFFQRkMjK3iieicjR460N998073+ww8/tBYtWrjaLqeffrrdf//9Nn78+Kjb16NHD1fT5bvvvnPFdrt27eqCLPFGcAUAAAAAALhRfzSlRiP9KCgSyb59+1zwJDnKMlGwJpq2RKJgTZMmTULmKUjjt27duiQ1Wfbs2ZNkXqzRLQgAAAAAAMADgisA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ddVVVqlSJfvwww8tXvSDy7+NNQ0ZMiSwTvzT5MmT3bLjxo2zhx9+OG5tRebh2M/8Y79gwYLuYkkBl2XLlllm03fNzN8hEydO5HyCHHvuyonnLcSejylrTGPHjvVNnTo1ZN5VV13lk5IlS7rHFSpUcI9r1KgRWObqq6/2LViwwHf48GHfpk2bfAMHDvTlzZs38Pxtt93mW758ue/gwYO+HTt2+GbOnOk79dRTA8+3bdvW98svvwReP2LEiGTbKGpDpOcaNGjgnh80aJBv3bp1vgIFCgSe++eff3xt2rRJ9jsUK1bMzdN7BL9X48aNfYsXL3Zt//rrr32lSpXyNWnSxPfrr7/69uzZ45swYYLvlFNOSXEdTp8+3Tdv3jz3nfUarY/g55s1a+bbv3+/r3DhwhG/l5bfunVrxO/atGlT37Jly3yHDh3yzZ8/33fhhRcGlilevLhv4sSJvr///tt34MABtw3uvPPOJO+d0rZ54IEH3HfV+69atcrXqVOnwHPh69HfJq1LPdb61nrXOtR77Nu3z/fVV1/5ypQpE9KGlD7DP/3555++du3axf0YYcod55nbb7/dLRf8/sGT9vW1a9cm+/q+ffv6lixZkmR+1apV3fs+++yz7rG+n5QvXz7FdSY6T0S7jvXdpkyZErFNrVu3dm3fvXu3b9KkSSHnHZ0zhw8f7s43Oh6/++47X+3atVP8rNmzZ/uGDRuW7OPU1omms88+233Hc889N+77J1PGTRz78Tn2g6fkjs/gyb8Ngmnb+V+vc4R+Z+3cudO3efNm953D29yxY0ffJ5984n7b+J+/5ZZbfIsWLXLnFv1Nf/rpp0PW8yOPPOK2o16zfv1638iRI32JiYkh763fFfp9p980H330ka979+7ud0bwMvXr13frulChQnHf55lyx7mL85a38xaTxXQicyULS0xMtNatW7uuLeqeEYmip19++aX9/PPPrq9hp06dXJbFk08+6Z5XFHTSpEkuVfP888+3a665xj766KNA//qOHTvayJEj7a233nLpdbfccov973//89Tul19+2UVX//Of/5hXyjp56KGH7Morr7Szzz7bpkyZYt26dbO7777bbrrpJmvcuHGqn6O0OaXVKX3vvffes7Zt24Y8r8cffPCB7d+/P+Lr69evb4sWLYr43ODBg61Hjx5Wp04dd2fqs88+C9wlKlSokHud2qnottbxu+++65aNZtvoO/bv3991SdLzvXv3tmeffTZNWUGnnnqqi67fe++9dvXVV7uIuO5g+0X7GT/99JNbD8h5suJ5Rndl9u7d61LoY0lp+br7pTs+omNWn3H77bfHtD6CjpWFCxcmma872rfeeqvdfPPNbtLdrJ49e4Z0J7jttttcdx2l1msdqdtORmeNKctly5YtHOO5DMd+5h37aT0e/e2sUqWKW8fqHuCn88OBAwfs8ssvt8cff9yefvppu+6665L8dpo6dapb59o26qajLr7qvnDBBRe42ii6466//X7qxqSMkwsvvNB9hmrH6Jzkd9lll7m786+++qpdcsklrguifz8Ipu+v30FqHxCPcxfnLcRb3CM8TP8vMnvs2DGXYaBJNm7c6KtZs2ZgmfDo7HPPPeeyDYLfR5kHe/fu9SUkJLjXphTlVFaFPyIazRRN5ooyJzp06OAiwUWLFvWUudKwYcPAMk888YSbV7FixcC8119/3WVjJBfdbtSokbtL8+KLL7rHderUcevYn72hTJijR4+6CHdy31nvN3r06IjftWXLloF5p59+urubc8cddyT7Xp999plv8ODB7v+pbZs//vgjSaZLnz59fD/88EPUmSvhd6O1b+hOV7Sf4Z9eeukl3zfffBP3Y4Qp559nSpQo4fvrr7/cZya3THrvAmnS3Ssdp/7HnTt3dndqldWm7Lgnn3wy5ByTnrtAOt8pQyW8TeEZcrr7rIw3/V93zI4cOeK76667As/ny5fPrbtHH300QzNXNOmOtu5kx3v/ZMq4iWM/Psd+WjNXIv09D3793LlzQ+bp7ry+W3Cbhw4dGrKM7sr37NkzZN4999zjtn9ybdCd/e3btwceK1P4888/D1lG2XfhmSualFVz3333xX2fZ8od5y7OW97OW0wW04mwVxajOwG6I6BJGQ66a6mopTIOIlHEdf78+SHzfvjhBytSpIidddZZrn/vrFmzXH88ZX08+OCDgX55pUqVcqPAfP3118m2R5Hfffv2BSZZuXJl4LH6CEaiuxuKJj/xxBMe1obZ8uXLA/9XPRHdrQnut6h5Z5xxRshrdEdYbTt8+LBbd6oroLs4oii22u8vrqvIt4pRzZ07N8WaK3qvSILXvYr2KsqsbSKKKitKru+gdaE2aWQO/7ZMadso46Ry5cpuPQavf72f7n5HS+tr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", "text/plain": [ - "
" - ], - "image/png": "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lpYWUaj7mWeeyfi5oihC2qlramoSKR60fWgbkJhPBt1wo+1/+PBhIa60Hu9617vE9qZ1tH8A0LqedNJJQuDf+9734sc//rEQ4qqqquS0aH//8pe/FOJHf9O0SRzPPvvsSZeBlpmgHxC5giT9G9/4Bl73uteJpx5zCW2DlStXivWj/U3bi+osUk0e2hd0DFL6ER3Pb33rW6ecHm1DklT68UL84he/EOk443+IHHfccVi9erXYxqk8/fTTYvzxwzMMwzBMocOeyZ5ZjJ6ZDToO6Ybyeeedl+xHT5JSWZxcetpLL70k5pOtPirDMIUNNVvGHXfcAeYVV1xhEps3bzavueYac2hoyHS73eKz3/3ud+bf//538ffevXvNP//5z2nj2sPZnaqq5nPPPWfed999af2JSCRiLlu2LNlv/fr1ov973/veZL8//vGPZigUMtva2pL91qxZY8bjcTGs3a+9vV30u/baa9Pms27dOjMWi6X1v+mmm8Sy2+8XL14sphUMBs2WlpZk/+OPP170/9///d+0cYmvfOUrafN53eteJ/p/+tOfTut/yy23mLqup63nyMiImM509sXZZ58tpnvBBRdM+Oz+++83M/Hiiy+aS5YsybhPaV3pfV1dndj+d911lylJUnI42t/ElVdeKd5v2LBBvL/sssuyLuPy5ctNTdPMW2+9NW1a0+nuuecec3Bw0KysrJx0uM9//vNp+yxTd8YZZySXNRAImH19feZtt92Wtu9o248/Dmna46dF80rdR/b2u/POO9OGe/jhh8X+/f73v5/sJ8uyeeDAAbF/xs/rxhtvNGtra8X2p+Pr3nvvFf0//OEPi2Fouel4v/7669PG/eY3vymW3ev1pvU/6aSTxPhvfOMbZ/Vd54477rjjjrv57tgzrf7smcXrmdmGefbZZ4V/2u9f9apXieOGOnLGr371q+a5554rjtvx42Y63rN127dvN//617/m7DvJHXfcYV46fuKRYSapI0LFoy+88ELxWD+9Zkt/IVILgVOkkiLAVAslUyvDVBeGWsezoVQYSkWgKCchy7JIibnttttEVNOGUksoOprK61//ejE8La8dmaWO0kEoKjudBjhoPhSVtXnyySdFvZtMUfQf/OAHae9pGIq6f/vb307rT3VfaLkogj8baB0ISuvNBKUEv+pVrxIdNVBCTwLQNqdC2pPVRqLh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" + "
" + ] }, - "metadata": {}, - "output_type": "display_data", "jetTransient": { "display_id": null - } + }, + "metadata": {}, + "output_type": "display_data" } ], - "execution_count": 6 - }, - { - "cell_type": "code", - "id": "timing-bars", - "metadata": { - "trusted": true, - "ExecuteTime": { - "end_time": "2026-03-04T09:18:07.538518Z", - "start_time": "2026-03-04T09:18:07.480603Z" - } - }, "source": [ - "labels = [\"NumPy\", \"Blosc2+DSL\"]\n", - "first_times = [t_numpy_first, t_dsl_first]\n", - "best_times = [t_numpy, t_dsl]\n", + "labels = [\"Blosc+NumPy (baseline)\", \"Blosc2+DSL (no JIT)\", \"Blosc2+DSL (1 thread)\", \"Blosc2+DSL\"]\n", + "first_times = [t_numpy_udf_first, t_dsl_no_jit_first, t_dsl_single_thread_first, t_dsl_first]\n", + "best_times = [t_numpy_udf, t_dsl_no_jit, t_dsl_single_thread, t_dsl]\n", "\n", "x = np.arange(len(labels))\n", - "width = 0.36\n", + "width = 0.28\n", "\n", - "fig, ax = plt.subplots(figsize=(10, 5), constrained_layout=True)\n", + "fig, ax = plt.subplots(figsize=(11, 5), constrained_layout=True)\n", "ax.bar(x - width / 2, first_times, width, label=\"First run\", color=\"#4C78A8\")\n", "ax.bar(x + width / 2, best_times, width, label=\"Best run\", color=\"#F58518\")\n", "\n", "ax.set_xticks(x)\n", "ax.set_xticklabels(labels)\n", "ax.set_ylabel(\"Time (seconds)\")\n", - "ax.set_title(\"Mandelbrot Timings: NumPy vs Blosc2 DSL Backends\")\n", + "ax.set_title(\"Mandelbrot Timings: Blosc2+NumPy vs Blosc2 DSL Backends\")\n", "ax.legend()\n", "\n", - "speedup_first = first_times[0] / first_times[1]\n", - "speedup_best = best_times[0] / best_times[1]\n", + "speedup_first = [first_times[0] / t for t in first_times[1:]]\n", + "speedup_best = [best_times[0] / t for t in best_times[1:]]\n", "for i, t in enumerate(first_times):\n", - " label = f\"{t:.3f}s\"\n", - " if i == 1:\n", - " label += f\" ({speedup_first:.1f}x)\"\n", + " label = f\"{speedup_first[i - 1]:.1f}x\" if i > 0 else f\"{t:.3g}s\"\n", " ax.text(i - width / 2, t, label, ha=\"center\", va=\"bottom\")\n", "for i, t in enumerate(best_times):\n", - " label = f\"{t:.3f}s\"\n", - " if i == 1:\n", - " label += f\" ({speedup_best:.1f}x)\"\n", + " label = f\"{speedup_best[i - 1]:.1f}x\" if i > 0 else f\"{t:.3g}s\"\n", " ax.text(i + width / 2, t, label, ha=\"center\", va=\"bottom\")\n", "\n", "plt.show()" - ], - "outputs": [ - { - "data": { - "text/plain": [ - "
" - ], - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/MAAAH/CAYAAAAboY3xAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAZExJREFUeJzt3Qm4jPX///H3sVaWVGQrEpUWZE8RZYk2hbTbSoXKUokoW5EWKu2UpZUoKWUriSxF9khEZF+yZKf5X6/P73vPf2bOnGPOcY459/F8XNfnOmfuueee+77nnuX9Wd6fBDMLGAAAAAAA8I0s8d4BAAAAAACQMgTzAAAAAAD4DME8AAAAAAA+QzAPAAAAAIDPEMwDAAAAAOAzBPMAAAAAAPgMwTwAAAAAAD5DMA8AAAAAgM8QzAMAAAAA4DME8wCQgfTo0cMCgUCqHjt06FBbvXp18Hbx4sXdth577DHzq+bNm7tj0LFkhHMMpNd1ifjyPhfOOussywgiP88BIBqCeQAn3Q9wlauuuirqOmvXrnX3f/XVVyd8//yua9eu1rBhw2OuN3Xq1ODrkFzRj2uEV8yoNGrUKMMFIgo6Ql+7zZs3248//mi33HKL+UXkdXnw4EH7888/7Z133rFzzjnHMpKEhAT3efbll1+6z6x///3XFi9ebN26dbOcOXOm+DU7evSo/fPPP7Zo0SJ3vFWqVIn6mFy5clnPnj3dc+k5t23bZvPnz7dXXnnFChcunCbXY+TngJ5n6dKl7thOPfXUFG8PADKzbPHeAQA40fbv32933XWX/fTTT2HLa9asaeeee64dOHAgbvvmZ0899ZSNHj3aBRjJee6552zIkCHB25UrV7b27du75cuWLQsuV2ChH/GffvqpC6zSyrPPPmvPP/+8+dUzzzxjn3/+uWU0Cupefvll93+RIkXswQcftC+++MIeeughFyD6wbp161yllOTIkcMuueQSt//XXXedXXzxxe6zIyM47bTTbNiwYTZr1ix7++23bcuWLVatWjXr1auX1a5d26699toUv2Z58uRxx3jbbbfZAw88YAMGDAjr1ZMtWzZXQVO6dGkbPny4DRo0yHLnzm2XXnqp+zzVa71x48Y0Ob5JkybZiBEj3P96jho1arj3bbly5axp06Zp8hwAkBkQzAM46XzzzTfuB+ujjz7qWqQ8+kE6d+5cy58/f1z3L6NQwLBv37403+6UKVPCbqvyRMH85MmTbdq0aYnWT8tAXvSah77ufqLgq3z58nbrrbe64CkjWb9+vX300UfB2wrGVq5caR07dvRNML9r166wY/BasN944w3Xmyfy2o2XQ4cO2ZVXXumCeY8qyNasWWO9e/d2Af13332X4tdMnnzySfv444+tU6dO9scff7jKAlEviwoVKrjPyU8++STsMeoNoMqPtLJixYqw/dL1o+2rV4qeK60/EwDAr+hmD+Ckox+i6v5Zt27d4LLs2bNbkyZN3I/YaNRCpZZ8dStVgKugv3HjxonWU7dQtVipu7m6oipQXbJkiWvZi6Tg4Oeff3atfQp61BqWlLvvvts9p557+/bt7hhS0vW3Q4cO7oe+Hv/DDz+41rTI8Zl79uyx888/38aPH2+7d+8O/phWUP/SSy+57rw6nuXLlycah6/jVgtaixYtgt1jtc30GJus4ErDINST4pdffnHHpFZ83RYFurqt86pzdvnllx9zzHxKXjfveUNft2jbrFOnjk2fPt11X9a51XlT74NQ6gly0UUXxXw+1Evh999/d63zx6LzFO01UHdyldDj0b6rgkvb/fvvv93r/9lnn1nevHldEDVw4EDXdV7H8f7778cUuGl99bQoUaKEu62W5K1bt7oW3kgTJ0505ycpem303NG6Wes9qxbhLFn+7ydNxYoVbcKECe65dG2oq/x7771nqbVp0yb398iRI8dct02bNu660fWjQPn111+3008/PWydUqVKuR4s2mddQ+oNoPezznXke37OnDm2d+9e27Fjh6vo8j6zDh8+HBbIe7wKHrWwp5b2/d5773WfM+ra7ilZsqT7G9mjSRRc6/VJT3oddJ2Gvg7Vq1e3UaNG2V9//eX2W59R6lFwyimnJHq83mcjR450vRh0Xeh6U2t/cooVK+YqNPSZcPbZZ7tlej31fvA+D3V/586d3dCHaPlKWrdu7T4ntK4+7ytVqpToebzPHV0P+pvU8JTbb7/dfabp/amKJ33OqVIawMmLlnkAJx0FtfohfOedd7of/dKgQQP3I03BUrQfR2o5HjdunAtwFcjccccd7gf5DTfc4Fr6Q+kHplqQ3nzzTfcDV9sbM2aM+2GoH+Vy2WWXua6kCjg0BlUBjrrIKgCK1n29T58+7kerWt8KFChgjzzyiOvyqlZa/ahLTrNmzVwXWrUu6keujuX777+3MmXKuB+2Hu2DgqoZM2bY448/HmyV13Ffc801LiBasGCBC3AV3BctWtS13sk999zj9k0/Vt999123bNWqVZZeFBApiFOL3Ycffuj2VwG+ukT37dvXnXtRl2mdN/2QP1bSu1heN1UM6JpRIKYAPmvWrC4A1usYSt2zv/76a/djW/cr2NE+R+ZqUOt1rVq1wgKB5KhHgQKQDz74IM1b53WuFExoCIL2VdeYgsb//vvPzjjjDHedXnHFFdayZUtXUaBrMjm6nlRZoaBQtM+qnNH1owojT8GCBV23cF3/SVEQ9vDDD7v3m953HgX3N910k6so0H7qveG9r3QcO3futPPOOy9qnoFo9Hp647xVwaegWPulgC1aEBtK14POkXqYvPXWW+6aU3CvYSR63RWEapt6j6l1WRUUClD1PrrxxhstX758LkgTXTN6Xj2n/ldLfNWqVd150vaTUqhQIfdXlY7HQxUIurbuv/9+dy3/9ttvLmD2Pk+OFQQfL31Oea+Dxunr/Ona0Xs+tFeNKqBU2ajzretMY/113aqiM7Q7vj7rVLGm61mfT/oOUOWErp3u3btH3QdVbOpzUu99VaJo+7reVKmi10yfPQro1UOiX79+LmeAeqGEUi8GffZqXX3+KOjXEBlt26uU0Lb1OaNzrPegjluVcKpUi6wc1PeTeoeo94To+tS5ee2119Lw7APwG/26oVAolExfmjdvHpCKFSsG2rZtG9i1a1fglFNOcfeNHDky8N1337n/V69eHfjqq6/CHuut55Vs2bIFFi1aFJgyZUrYcjlw4EDg/PPPDy4rU6aMW96uXbvgss8//zywb9++wLnnnhtcVrp06cDhw4fdut6yYsWKuWVdu3YNe55LL700cOjQobDlQ4cOdfvu3S5evLjb1t69ewNFihQJLq9cubJb/vLLL4c9Vvr27Rv2PDfffLNb/tRTT4UtHzVqVODo0aNhx7lnzx63nZS+Lo0bN3bPUbNmzSRfMx2Lt0zHKFdccUVwWd26dYPHGnpOW7dunWjbPXr0CDvHKXndvvzyy8C///4bKFy4cHBZyZIl3WsRus327du722eddVayxz516tRE+xKteK/lY489FsiSJUvg999/D8yfPz/RMYU+n85TtNdDz6ni3da5EV3Puq695R999JF7jcePHx/2+J9++insOvOe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2HvvvZfkOvv27bNNmzZZvnz57LrrrrMvv/wy6pj7uXPn2qJFixLdp673CuIfeOABu/32261JkyZ2xhlnpPmxAAAAAH6jXq0a0hpa1BNW3ewHDhwYXE8Bfvfu3W348OG2a9cue/fdd11AP2jQINeTVrmt1qxZY126dAmuL+p9qxZ673ak4sWLu/uV4+qHH35w27n77rutR48ermEuckas0O2oZ8EXX3xhjz32mNuHbdu22euvv35CKgMyfnUDECO1npcqVSp4W63h5cqVsx07dti6deusb9++VrRo0bDkdF4QPnv2bFu6dGmibdarV891s1cXeW37xRdftOXLl7s3bag8efLYbbfd5t7EkTTGZ+PGje6DQB9KWk+31a0fAAAAQOwef/xx6927t/Xq1euYyaYrV65sW7dutRYtWtiECRPs6NGjyW77+eefd7/n9btdSbAffPDBmPbpmmuucb/v9Vcxw8iRI91Y/8geu2mNYB6ZRqVKlVxNmserxdOYdc39riR3SkYXKm/evNa4cWNXwxbN6aefbv369XNz0KtSQHPHd+vWzY4cORK23h133OGCfo2nj9Yq37lzZ9f6rw+QX375xa6//npX+wcAAACc7G688Ub3mzm0272C82i+//57GzBgQEzJptVKLmpEU2v/sbzyyiuulT2lNBzg4Ycfdg13agQcP3681a5dm2AeiNW0adNcQJ0UBfSRdu/e7Vr0k6Kx7bGMbx88eLAr0ehNnN5vZAAAAMCv1J2+TZs2wdt79+5Ncl0Naw01bNgwmzx5sgui1fr+9ddfu9upEbntWKmHb2iCbLXSlylTxtIbwTwAAAAAIG4UvK9atSrmdaMlm27QoIHVqVPH5amaMmWKG9qamv0IpQA9srEwWtK9yCTa6oGbJUv6p6cjmPe5Ez1vJJDR5yMFAADAyWXP/5JNq4wePdomTpzokk2r+/uhQ4csa9asqdquxtsrs34oTTmdURDMAwAAAAB8qeMxkk0ru73Gr//0008ua35KklArH1eBAgVc/itVEtSvX9/1ANBQ3YyAYB4AAAAAMqnM3gNyzzGSTSs7vRLmtW7d2tavX++65MdKs1i1bdvWnnrqKXv66addMuyXXnrJTTedEWgAACm1I2iaMdW2KNN5aFbFjIhu9sgMMvuXDAAAQHrSPOl9+vRxAedff/0V793Bcb5mscaj6T8q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AJzoYD4hIcFKlSplZ599tmXJEj70fvr06andLAAAAAAASI9gXuPjP/74YytevLgL6kMFAgHLli3VdQQAAAAAAOAYUhV1v/322zZ37ly74YYbbOPGjS6ABwAAAAAAGTiYv+CCC6xJkya2atWqtN8jAAAAAACQ9vPMazo6jZcHAAAAAAA+aZkfNGiQvfzyy1aoUCFbvHixHT58OOx+LQMAAAAAABkomB8zZoz7+/777weXady8kuGRAA8AAAAAgPSVqqi7RIkSab8nAAAAAAAg/YL5tWvXpuZhAAAAAAAgDaS6P/z5559vHTp0sIsvvtjd/u233+zVV1+1P//8My32CwAAAAAApGU2+3r16rngvUqVKrZo0SJXqlatakuXLrU6deqkZpMAAAAAACA9W+aff/55GzhwoHXt2jVseb9+/ax///5WsWLF1GwWAAAAAACkV8u8uta/9957iZYru/0ll1wS83Zq1Khh48aNs/Xr17ss+A0bNjzmY2rWrGnz5s2zAwcO2B9//GHNmzdPtE7btm1t9erVtn//fps9e7ZVrlw55n0CAAAAACBTBvNbt261yy+/PNFyLduyZUvM28mVK5ctXLjQ2rVrF9P65513no0fP96mTp3qnuuVV16xIUOGuG7/nqZNm9qAAQOsV69eVqFCBbf9iRMnWoECBWLeLwAAAAAAMl03+8GDB9u7777rkuDNnDnTLbvqqqvsySefdIF0rCZMmOBKrB566CHX4v7444+728uXL7fq1atbx44dbdKkSW5Zp06d3P4NGzYs+JgbbrjBWrVq5YYAAAAAAABwUgbzffr0sT179thjjz3mxsnLhg0brGfPnvbaa69ZeqlWrZpNmTIlbJla3dVCL9mzZ3fj9b19EnXf12P02KTkyJHDcubMGbydJ0+edNl/AAAAAADi1s1eFECfe+65dvrpp7ui/9MzkJdChQrZ5s2bw5bptp7/lFNOsfz581u2bNmirqPHJkWJ/Hbv3h0sGsMPAAAAAECmCuY1dr1UqVLu/3///dcV0bLixYub36glP2/evMFStGjReO8SAAAAAABpG8xrPPqVV16ZaLnmmvfGqqeHTZs2WcGCBcOW6fauXbtcdvtt27bZkSNHoq6jxybl0KFDbthAaAEAAAAAIFMF8+XLl7effvop0XJNAxcty31amTVrltWuXTtsWd26dd1yOXz4sJu2LnSdhIQEd9tbBwAAAACAkzKYV1K5aEniNHY9a9asKZqarly5cq5IiRIl3P8afy99+/a14cOHB9d/++23XQZ9ZaW/6KKLrE2bNm4quoEDBwbXUTb91q1bW7Nmzax06dL21ltvuecZOnRoag4VAAAAAIDMkc3+xx9/dEnj7rzzTvvvv//csixZsrhlM2bMiHk7lSpVsh9++CF42wvK1VW/ZcuWVrhwYStWrFjw/jVr1rhp5rRe+/bt7e+//7b7778/OC2djBo1ys0p37t3b5f0bsGCBVa/fn3bsmVLag4VAAAAAIAMJ0EN7Sl90MUXX+wC+p07d9r06dPdsho1arjkcddee60tXbrU/Ey9DpTVXseT0cfP39jl03jvAnDcRubpEO9dAI5Lrm5J52UBAABIj3g0Vd3sly1bZmXLlnWt4GeffbZ7shEjRrhu7X4P5AEAAAAAyJTd7GXjxo3WrVu3tN0bAAAAAABwTKlqmZfq1avbBx984LLaFylSxC2755577KqrrkrtJgEAAAAAQHoF840aNbKJEyfa/v37rUKFCpYzZ85gNvunnnoqNZsEAAAAAADpGcx3797dHnroIXvggQfc3O4etdIruAcAAAAAABksmNcc78pmH2nXrl2WL1++tNgvAAAAAACQlsH8pk2brFSpUlHH0f/555+p2SQAAAAAAEjPYH7w4MH26quvWpUqVSwQCLgEeHfddZe99NJL9tZbb6VmkwAAAAAAID2npnv++ectS5Ys9t1339lpp53mutwfPHjQBfOvv/56ajYJAAAAAADSe575vn372osvvui62+fOndt+++0327t3b2o3BwAAAAAA0nueeVEm+2XLltny5cutTp06Vrp06ePZHAAAAAAASK9gfuTIkdauXTv3/ymnnGK//PKLjRo1yhYtWuTmoAcAAAAAABksmL/66qtt+vTp7v9bb73VjZ/XlHSPPvqom4MeAAAAAABksGD+9NNPtx07drj/69evb2PGjLH9+/fb+PHj7YILLkjrfQQAAAAAAMcbzK9bt86qVavmMtkrmJ80aZJbfsYZZ9iBAwdSs0kAAAAAAJCe2exfeeUV++ijj+zff/+1v/76y3744Ydg9/vFixenZpMAAAAAACA9g/m33nrL5syZY8WKFbPJkydbIBBwy//880/GzAMAAAAAkFHnmf/1119dCfXNN9+kxT4BAAAAAIC0GDP/5JNPumnoYlGlShW7/vrrY900AAAAAABIj2D+kksusbVr19obb7zhkt7lz58/eF/WrFmtTJky1qZNG/vpp5/cPPR79uxJyX4AAAAAAIC07mbfvHlzK1u2rD388MP28ccfW968ee3o0aN28OBBl9Ve5s+fb0OGDLFhw4a55QAAAAAAIM5j5hctWmQPPPCAPfjggy6wL168uJ166qm2bds2W7BggW3fvj0ddhEAAAAAABx3Ajxlr1+4cKErAAAAAAAgg46ZBwAAAAAAGQPBPAAAAAAAPkMwDwAAAACAzxDMAwAAAABwMgXzJUuWtHr16tkpp5ySdnsEAAAAAADSPpg/88wzbfLkybZixQr75ptvrHDhwm75e++9Zy+99FJqNgkAAAAAANIzmB84cKAdOXLEihUrZvv27QsuHzlypNWvXz81mwQAAAAAAOk5z7y61l933XW2fv36sOV//PGHFS9ePDWbBAAAAAAA6dkynytXrrAW+dDu9wcPHkzNJgEAAAAAQHoG89OnT7dmzZoFbwcCAUtISLDOnTvb1KlTU7NJAAAAAACQnt3sFbR/9913VqlSJcuRI4e98MILdumll7qW+auuuio1mwQAAAAAAOnZMr906VK78MILbcaMGfbll1+6bveff/65lS9f3v7888/UbBIAAAAAAKRny7zs3r3b+vbtm9qHAwAAAACAEx3M58yZ08qWLWtnn322ZckS3sD/1VdfpXazAAAAAAAgPYJ5TUs3YsQIy58/f6L7lAwvW7ZU1xEAAAAAAID0GDM/aNAg++yzz6xw4cKWNWvWsEIgDwAAAABABgzmCxYsaAMGDLAtW7ak/R4BAAAAAIC0D+ZHjx5ttWrVSs1DAQAAAADAcUpVn/iHH37YdbOvUaOGLV682A4fPpyoGz4AAAAAAMhAwfydd95p9erVswMHDrgWeiW98+h/gnkAAAAAADJYMP/cc89Zjx497Pnnnw8L5AEAAAAAQAYdM58jRw4bOXIkgTwAAAAAAH4J5ocPH26333572u8NAAAAAABIn272mk++c+fOdt1119miRYsSJcB77LHHUrNZAAAAAACQXsF8mTJlbP78+e7/yy67LOw+ut4DAAAAAJABu9lfe+21SZbatWuneHtt27a11atX2/79+2327NlWuXLlJNedOnWqqzCILF9//XVwnaFD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BPMAAAAnqaZNm9qAAQOsV69eVqFCBVu4cKFNnDjRChQokORjqlWrZp988om99957Vr58eRe0qlx66aVh6yl4L1SoULCEto7PnDkz7D6VwYMH259//umC6LTy6KOPBisWPPPmzbOWLVvaxRdfbNddd50lJCTYpEmTLEuW8J/F77//vo0cOTLm54p1u8n56KOPrHr16nbJJZfE/BgAJ68EM/v/n25w8uTJY7t377a8efPanj17LCO7scv/r2kG/Gpkng7x3gXguOTqtineuwCkilrif/nlF3vkkUfcbQWg69ats0GDBiXZQqxW7ly5crlWb8+sWbNswYIF1qZNG3dbAXS+fPns1ltvjWk/smXLZuvXr3fP++yzz7plxYoVs9dff90Ftzly5LA1a9bYE088EdbCn5z8+fPb5s2brUyZMvbbb78luZ7uV++CkiVLusqEUD169LBbbrnFVVqkVOR2n376aXvooYfc8h07drh1vv76azvttNOsdu3awQqH7777zn766Sd75plnUvycAE6ueJSWeQAAgJOQxmhrfPaUKVOCyxRQ6rZa35Oi+0IfI2rNj3xMrVq1XDC9fPlye/PNN+3MM89Mcps333yznXXWWa4SwPPGG29Yzpw57eqrr3YB8JNPPmn//vtvzMenSgB1eddwgKQokFZruoJtVWKklWjbfe6551yFxJAhQ4LDG6688kpr3rx5WM+Bn3/+2WrUqJFm+wIg8yKYBwAAOAmp5Vot4gq4Q+m2ur0nRfcd6zETJkywZs2auRZnBeEaI68W9aS6nN93332uQkCt8x61zKuFesmSJW5M//jx42369OkxH1/x4sXdfoUGyh71IFBr1969e914/7p167ox68crue3+999/ds8997hz0q9fP3vxxRetXbt2iSoRNmzY4PYd8FsSR6+XTqNGjeK9GycNgnkAAACkKY01/+qrr1wg/uWXX9qNN95oVapUca31kYoWLerGmGsMfqjXXnvNunfvbjNmzLCePXu61vmUOPXUU10Cu6TGpqvrvFr9V6xYYaNGjXK9AI7XsbarSonHH3/cunTpYuPGjXO5ByIpqaBa9hHfJI4aYqH71RtEwyImT57sruFoNAxEiRJVcVSuXDlLS9GSOIb65ptv3PM2bNgwbLkSSaoHzT///OP2XxVsZcuWTfa5WrdubVOnTrVdu3a5bZ5++ukp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s2bNajfffHPw/gIFCtgNN9xg77//fqr3EwCAeMqVK5fdc8899scff9j27dsT3a/v0m+++cZ++eUXN46+TZs2dt9991n37t3d/WoB/+STT9x34cUXX2y1atWyzz//3BISEtz9Dz30kL3xxhv27rvvuq78+h5duXJlkvujlvLdu3e73wNp6ffff3c9EtQKL+p+r+do0qSJZclCqAIcS9xrFCgUin9aC2bOnBkYMmRIqlvmdTshISG4bNmyZYFp06YFb2fJkiWwZ8+ewO233x619v3UU08NvP7664HDhw8HypQp45a98cYbgfHjxwfX79ixY2DlypVxP28UCoVCocRa9B2p7zZ9B6rI+vXrA+XLl3f3R7bMP/vss+47NHQbbdq0Cezevdt9z+pxybVw//3338HW8GOVs846K7BmzRr3nEmtk9qWeRX1KNi7d2/wdtu2bQP//vtvYNeuXYHvvvsu0L1790CJEiXCHkPLPMUotMwDSBmNm2/evLmVLl06VY9funRpWNK8zZs3u3Fwnv/++8+1QJx99tlhj1Prwp49e1xp3Lixa33wHjd48GCrV6+ea6UQje0bNmxYKo8QAID4mDp1ql1++eWuVK5c2SZOnOharTXGPJJa22fNmhW27KeffrI8efLYOeecYwsXLrQpU6a478pRo0bZ/fffHxxzrh5s6gH33XffHXOftL3x48fbb7/9Zj179gy7z/teVvH2M3TZW2+9FdNxq7dA6G+DN9980/UsuPvuu90x3nbbbe73Q506dWLaHnCyIJgHkCLTp093Py769esXtlxBuNd1z5M9e/ZEj1cG21BeVtvIZZFd6zp27Oh+3OjLvXDhwjZixIjgfQsWLHA/Wpo1a2YVKlSwSy+9lGAeAOA7e/futVWrVrkyd+5cF4Cru33r1q1TvC19L9etW9caNGjgAnENk1OXdmWK94arHYuS0k2YMMEF5rfeeqtLyBfKq3hQ0b5u2LAhbNkzzzwT0/OoYiIyed6///7rhuFp2ICGEej3hzeEAMD/IZgHkGJdunSxm266yapVqxZcpjFuCrRD6Ys8rWzatMn9uFEm+2iGDBniWuRbtmzpWiL+/vvvNHtuAADiQZXbCspPPfXURPctW7Ys7HtYNOOLxrWHfgcqU7xa1MuXL2+HDh1yQbkCZQXPypifXIv8pEmT3GM0nv7gwYOJ1vEqHlQ0jZyC/dBl+m1wLMpWr0z5Y8aMSXY9TZOnig0A/1+2kP8BICaaWkbzvz766KPBZT/88IPrtte5c2cbPXq0+2JWa4B+VJwIH3/8sb300kuu9UIt9AAA+E3OnDmtYMGCwbneH374Ydc6/tVXXyVaV13RO3ToYIMGDbLXX3/dBcW9evWyAQMGuEqAKlWquGBdAfmWLVtckll9T6sSQBTgK+ms7lMXeQXvqgzQtrxA/rTTTnNJ+DS/vDfHvAJ0VTCkhqad0/Gp991ZZ53lkvKptV097F588UW3jlrhdRwffPCB61GgygRNe9eqVSvr379/2PY0Ra3WD6WEgfv27UvV/gF+FPeB+xQKxX/T5SgRj6aS8RLgqTz44IOBv/76yyXuGTZsmJuCLtrUdKHbmTp1amDgwIFhy/QYJc9LaZKb4cOHR52mjkKhUCiUjF70HRlKyd807VyjRo3c/Smdmq506dKBb7/9NrB58+bA/v37A8uXLw+0a9cu7Dk1xauS6B08eNAl23v11VeDyeySov1IbQI8jxL96fv6xx9/dN/3od/bSrb3yiuvuCn1lMxP52HhwoWBTp06hSXQTYqm84v3a0mh2AkqCf/7BwB8T93rlSCnffv28d4VAAAAIF0RzAPwPWXnVVc9de+/5JJLbMWKFfHeJQAAACBdMWYegO/Nnz/fjS3UtHkE8gAAADgZ0DIPAAAAAIDPMDUdAAAAAAA+QzAPAAAAAIDPEMwDAAAAAOAzBPMAAAAAAPgMwTwAAAAAAD5DMA8AAAAAgM8QzAMAAAAA4DME8wAAAAAAmL/8P8YQqbkdUDTRAAAAAElFTkSuQmCC" - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 7 + ] }, { "cell_type": "code", + "execution_count": 7, "id": "a1e8dbea24ecc319", "metadata": { - "trusted": true, "ExecuteTime": { - "end_time": "2026-03-04T09:18:07.558194Z", - "start_time": "2026-03-04T09:18:07.539078Z" - } + "end_time": "2026-03-09T11:41:38.489362Z", + "start_time": "2026-03-09T11:41:38.460526Z" + }, + "trusted": true }, - "source": [], "outputs": [], - "execution_count": 7 + "source": [] } ], "metadata": { diff --git a/examples/vlarray.py b/examples/vlarray.py new file mode 100644 index 00000000..0e988c83 --- /dev/null +++ b/examples/vlarray.py @@ -0,0 +1,69 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +import blosc2 + + +def show(label, value): + print(f"{label}: {value}") + + +urlpath = "example_vlarray.b2frame" +copy_path = "example_vlarray_copy.b2frame" +blosc2.remove_urlpath(urlpath) +blosc2.remove_urlpath(copy_path) + +# Create a persistent VLArray and store heterogeneous Python values. +vla = blosc2.VLArray(urlpath=urlpath, mode="w", contiguous=True) +vla.append({"name": "alpha", "count": 1}) +vla.extend([b"bytes", ("a", 2), ["x", "y"], 42, None]) +vla.insert(1, "between") + +show("Initial entries", list(vla)) +show("Negative index", vla[-1]) +show("Slice [1:6:2]", vla[1:6:2]) + +# Slice assignment with step == 1 can resize the container. +vla[2:5] = ["replaced", {"nested": True}] +show("After slice replacement", list(vla)) + +# Extended slices require matching lengths. +vla[::2] = ["even-0", "even-1", "even-2"] +show("After extended-slice update", list(vla)) + +# Delete by index, by slice, or with pop(). +del vla[1::3] +show("After slice deletion", list(vla)) +removed = vla.pop() +show("Popped entry", removed) +show("After pop", list(vla)) + +# Copy into a different backing store and with different compression parameters. +vla_copy = vla.copy(urlpath=copy_path, contiguous=False, cparams={"codec": blosc2.Codec.LZ4, "clevel": 5}) +show("Copied entries", list(vla_copy)) +show("Copy storage is contiguous", vla_copy.schunk.contiguous) +show("Copy codec", vla_copy.cparams.codec) + +# Round-trip through a cframe buffer. +cframe = vla.to_cframe() +restored = blosc2.from_cframe(cframe) +show("from_cframe type", type(restored).__name__) +show("from_cframe entries", list(restored)) + +# Reopen from disk; tagged stores come back as VLArray. +reopened = blosc2.open(urlpath, mode="r", mmap_mode="r") +show("Reopened type", type(reopened).__name__) +show("Reopened entries", list(reopened)) + +# Clear and reuse an in-memory copy. +scratch = vla.copy() +scratch.clear() +scratch.extend(["fresh", 123, {"done": True}]) +show("After clear + extend on in-memory copy", list(scratch)) + +blosc2.remove_urlpath(urlpath) +blosc2.remove_urlpath(copy_path) diff --git a/pyproject.toml b/pyproject.toml index 81c8d52a..6244b0d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "numexpr>=2.14.1; platform_machine != 'wasm32'", "requests", ] -version = "4.1.3.dev0" +version = "4.1.1.dev0" [project.entry-points."array_api"] blosc2 = "blosc2" diff --git a/src/blosc2/__init__.py b/src/blosc2/__init__.py index 24449554..e32b2f48 100644 --- a/src/blosc2/__init__.py +++ b/src/blosc2/__init__.py @@ -37,6 +37,11 @@ from .version import __array_api_version__, __version__ +_PACKAGE_DIR = str(Path(__file__).resolve().parent) +if _PACKAGE_DIR in __path__: + __path__.remove(_PACKAGE_DIR) +__path__.insert(0, _PACKAGE_DIR) + def _configure_libtcc_runtime_path(): """Best-effort configuration so miniexpr can find bundled libtcc at runtime.""" @@ -530,6 +535,8 @@ def _raise(exc): from .embed_store import EmbedStore, estore_from_cframe from .dict_store import DictStore from .tree_store import TreeStore +from .batch_store import Batch, BatchStore +from .vlarray import VLArray, vlarray_from_cframe from .c2array import c2context, C2Array, URLPath @@ -713,6 +720,8 @@ def _raise(exc): # Classes "C2Array", "CParams", + "Batch", + "BatchStore", # Enums "Codec", "DParams", @@ -739,6 +748,7 @@ def _raise(exc): "TreeStore", "Tuner", "URLPath", + "VLArray", # Version "__version__", # Utils @@ -934,6 +944,7 @@ def _raise(exc): "validate_expr", "var", "vecdot", + "vlarray_from_cframe", "where", "zeros", "zeros_like", diff --git a/src/blosc2/_msgpack_utils.py b/src/blosc2/_msgpack_utils.py new file mode 100644 index 00000000..fd179b84 --- /dev/null +++ b/src/blosc2/_msgpack_utils.py @@ -0,0 +1,26 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +from msgpack import packb, unpackb + +from blosc2 import blosc2_ext + + +def msgpack_packb(value): + return packb(value, default=blosc2_ext.encode_tuple, strict_types=True, use_bin_type=True) + + +def decode_tuple_list_hook(obj): + if obj and obj[0] == "__tuple__": + return tuple(obj[1:]) + return obj + + +def msgpack_unpackb(payload): + return unpackb(payload, list_hook=decode_tuple_list_hook) diff --git a/src/blosc2/batch_store.py b/src/blosc2/batch_store.py new file mode 100644 index 00000000..984f043e --- /dev/null +++ b/src/blosc2/batch_store.py @@ -0,0 +1,937 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import copy +import pathlib +import statistics +from collections.abc import Iterator, Sequence +from dataclasses import asdict +from functools import lru_cache +from typing import Any + +import numpy as np + +import blosc2 +from blosc2._msgpack_utils import msgpack_packb, msgpack_unpackb +from blosc2.info import InfoReporter, format_nbytes_info + +_BATCHSTORE_META = {"version": 1, "serializer": "msgpack", "max_blocksize": None, "arrow_schema": None} +_SUPPORTED_SERIALIZERS = {"msgpack", "arrow"} +_BATCHSTORE_VLMETA_KEY = "_batch_store_metadata" + + +def _check_serialized_size(buffer: bytes) -> None: + if len(buffer) > blosc2.MAX_BUFFERSIZE: + raise ValueError(f"Serialized objects cannot be larger than {blosc2.MAX_BUFFERSIZE} bytes") + + +class Batch(Sequence[Any]): + """A lazy sequence representing one batch in a :class:`BatchStore`. + + ``Batch`` provides sequence-style access to the items stored in a single + batch. Integer indexing can use block-local reads when possible, while + slicing materializes the full batch into Python items. + + Batch instances are normally obtained via :class:`BatchStore` indexing or + iteration rather than constructed directly. + """ + + def __init__(self, parent: BatchStore, nbatch: int, lazybatch: bytes) -> None: + self._parent = parent + self._nbatch = nbatch + self._lazybatch = lazybatch + self._items: list[Any] | None = None + self._cached_block_index: int | None = None + self._cached_block: list[Any] | None = None + self._nbytes, self._cbytes, self._nblocks = blosc2.get_cbuffer_sizes(lazybatch) + + def _normalize_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("Batch indices must be integers") + if index < 0: + index += len(self) + if index < 0 or index >= len(self): + raise IndexError("Batch index out of range") + return index + + def _decode_items(self) -> list[Any]: + if self._items is None: + blocks = self._parent._decode_blocks(self._nbatch) + self._items = [item for block in blocks for item in block] + return self._items + + def _get_block(self, block_index: int) -> list[Any]: + if self._cached_block_index == block_index and self._cached_block is not None: + return self._cached_block + block = self._parent._deserialize_block(self._parent.schunk.get_vlblock(self._nbatch, block_index)) + self._cached_block_index = block_index + self._cached_block = block + return block + + def __getitem__(self, index: int | slice) -> Any | list[Any]: + if isinstance(index, slice): + items = self._decode_items() + return items[index] + if index < 0: + items = self._decode_items() + index = self._normalize_index(index) + return items[index] + max_blocksize = self._parent.max_blocksize + if max_blocksize is not None: + block_index, item_index = divmod(index, max_blocksize) + if block_index >= self._nblocks: + raise IndexError("Batch index out of range") + block = self._get_block(block_index) + try: + return block[item_index] + except IndexError as exc: + raise IndexError("Batch index out of range") from exc + items = self._decode_items() + index = self._normalize_index(index) + return items[index] + + def __len__(self) -> int: + batch_length = self._parent._batch_length(self._nbatch) + if batch_length is not None: + return batch_length + return len(self._decode_items()) + + def __iter__(self) -> Iterator[Any]: + for i in range(len(self)): + yield self[i] + + @property + def lazybatch(self) -> bytes: + return self._lazybatch + + @property + def nbytes(self) -> int: + return self._nbytes + + @property + def cbytes(self) -> int: + return self._cbytes + + @property + def cratio(self) -> float: + return self._nbytes / self._cbytes + + def __repr__(self) -> str: + return f"Batch(len={len(self)}, nbytes={self.nbytes}, cbytes={self.cbytes})" + + +class BatchStoreItems(Sequence[Any]): + """A read-only flat view over the items stored in a :class:`BatchStore`.""" + + def __init__(self, parent: BatchStore) -> None: + self._parent = parent + + def __getitem__(self, index: int | slice) -> Any | list[Any]: + return self._parent._get_flat_item(index) + + def __len__(self) -> int: + return self._parent._get_total_item_count() + + +class BatchStore: + """A batched container for variable-length Python items. + + BatchStore stores data as a sequence of *batches*, where each batch contains + one or more Python items. Each batch is stored in one compressed chunk, and + each chunk is internally split into one or more variable-length blocks for + efficient item access. + + The main abstraction is batch-oriented: + + - indexing the store returns batches + - iterating the store yields batches + - :meth:`iter_items` provides flat item-wise traversal + + BatchStore is a good fit when: + + - data arrives naturally in batches + - batch-level append/update operations are important + - occasional item-level reads are needed inside a batch + + Parameters + ---------- + max_blocksize : int, optional + Maximum number of items stored in each internal variable-length block. + If not provided, a value is inferred from the first batch. + serializer : {"msgpack", "arrow"}, optional + Serializer used for batch payloads. ``"msgpack"`` is the default and is + the general-purpose choice for Python items. ``"arrow"`` is optional and + requires ``pyarrow``. + _from_schunk : blosc2.SChunk, optional + Internal hook used when reopening an already-tagged BatchStore. + **kwargs + Storage, compression, and decompression arguments accepted by the + constructor. + """ + + @staticmethod + def _set_typesize_one(cparams: blosc2.CParams | dict | None) -> blosc2.CParams | dict: + if cparams is None: + cparams = blosc2.CParams() + elif isinstance(cparams, blosc2.CParams): + cparams = copy.deepcopy(cparams) + else: + cparams = dict(cparams) + + if isinstance(cparams, blosc2.CParams): + cparams.typesize = 1 + else: + cparams["typesize"] = 1 + return cparams + + @staticmethod + def _coerce_storage(storage: blosc2.Storage | dict | None, kwargs: dict[str, Any]) -> blosc2.Storage: + if storage is not None: + storage_keys = set(blosc2.Storage.__annotations__) + storage_kwargs = storage_keys.intersection(kwargs) + if storage_kwargs: + unexpected = ", ".join(sorted(storage_kwargs)) + raise AttributeError( + f"Cannot pass both `storage` and other kwargs already included in Storage: {unexpected}" + ) + if isinstance(storage, blosc2.Storage): + return copy.deepcopy(storage) + return blosc2.Storage(**storage) + + storage_kwargs = { + name: kwargs.pop(name) for name in list(blosc2.Storage.__annotations__) if name in kwargs + } + return blosc2.Storage(**storage_kwargs) + + @staticmethod + def _validate_storage(storage: blosc2.Storage) -> None: + if storage.mmap_mode not in (None, "r"): + raise ValueError("For BatchStore containers, mmap_mode must be None or 'r'") + if storage.mmap_mode == "r" and storage.mode != "r": + raise ValueError("For BatchStore containers, mmap_mode='r' requires mode='r'") + + def _attach_schunk(self, schunk: blosc2.SChunk) -> None: + self.schunk = schunk + self.mode = schunk.mode + self.mmap_mode = getattr(schunk, "mmap_mode", None) + try: + batchstore_meta = self.schunk.meta["batchstore"] + except KeyError: + batchstore_meta = {} + self._serializer = batchstore_meta.get("serializer", self._serializer) + self._max_blocksize = batchstore_meta.get("max_blocksize", self._max_blocksize) + self._arrow_schema = batchstore_meta.get("arrow_schema", self._arrow_schema) + self._arrow_schema_obj = None + self._batch_lengths = self._load_batch_lengths() + self._items = BatchStoreItems(self) + self._item_prefix_sums: np.ndarray | None = None + self._validate_tag() + + def _maybe_open_existing(self, storage: blosc2.Storage) -> bool: + urlpath = storage.urlpath + if urlpath is None or storage.mode not in ("r", "a") or not pathlib.Path(urlpath).exists(): + return False + + schunk = blosc2.blosc2_ext.open(urlpath, mode=storage.mode, offset=0, mmap_mode=storage.mmap_mode) + self._attach_schunk(schunk) + return True + + def _make_storage(self) -> blosc2.Storage: + meta = {name: self.meta[name] for name in self.meta} + return blosc2.Storage( + contiguous=self.schunk.contiguous, + urlpath=self.urlpath, + mode=self.mode, + mmap_mode=self.mmap_mode, + meta=meta, + ) + + def __init__( + self, + max_blocksize: int | None = None, + serializer: str = "msgpack", + _from_schunk: blosc2.SChunk | None = None, + **kwargs: Any, + ) -> None: + """Create a new BatchStore or reopen an existing one. + + When a persistent ``urlpath`` points to an existing BatchStore and the + mode is ``"r"`` or ``"a"``, the container is reopened automatically. + Otherwise a new empty store is created. + """ + if max_blocksize is not None and max_blocksize <= 0: + raise ValueError("max_blocksize must be a positive integer") + if serializer not in _SUPPORTED_SERIALIZERS: + raise ValueError(f"Unsupported BatchStore serializer: {serializer!r}") + self._max_blocksize: int | None = max_blocksize + self._serializer = serializer + self._arrow_schema: bytes | None = None + self._arrow_schema_obj = None + self._batch_lengths: list[int] | None = None + if _from_schunk is not None: + if kwargs: + unexpected = ", ".join(sorted(kwargs)) + raise ValueError(f"Cannot pass {unexpected} together with `_from_schunk`") + self._attach_schunk(_from_schunk) + return + cparams = kwargs.pop("cparams", None) + dparams = kwargs.pop("dparams", None) + storage = kwargs.pop("storage", None) + storage = self._coerce_storage(storage, kwargs) + + if kwargs: + unexpected = ", ".join(sorted(kwargs)) + raise ValueError(f"Unsupported BatchStore keyword argument(s): {unexpected}") + + self._validate_storage(storage) + cparams = self._set_typesize_one(cparams) + + if dparams is None: + dparams = blosc2.DParams() + + if self._maybe_open_existing(storage): + return + + fixed_meta = dict(storage.meta or {}) + fixed_meta["batchstore"] = { + **_BATCHSTORE_META, + "serializer": self._serializer, + "max_blocksize": self._max_blocksize, + "arrow_schema": self._arrow_schema, + } + storage.meta = fixed_meta + schunk = blosc2.SChunk(chunksize=-1, data=None, cparams=cparams, dparams=dparams, storage=storage) + self._attach_schunk(schunk) + + def _validate_tag(self) -> None: + if "batchstore" not in self.schunk.meta: + raise ValueError("The supplied SChunk is not tagged as a BatchStore") + if self._serializer not in _SUPPORTED_SERIALIZERS: + raise ValueError(f"Unsupported BatchStore serializer in metadata: {self._serializer!r}") + if self._serializer == "arrow": + self._require_pyarrow() + + @staticmethod + @lru_cache(maxsize=1) + def _require_pyarrow(): + try: + import pyarrow as pa + import pyarrow.ipc as pa_ipc + except ImportError as exc: + raise ImportError("BatchStore serializer='arrow' requires pyarrow") from exc + return pa, pa_ipc + + def _check_writable(self) -> None: + if self.mode == "r": + raise ValueError("Cannot modify a BatchStore opened in read-only mode") + + def _normalize_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("BatchStore indices must be integers") + if index < 0: + index += len(self) + if index < 0 or index >= len(self): + raise IndexError("BatchStore index out of range") + return index + + def _normalize_insert_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("BatchStore indices must be integers") + if index < 0: + index += len(self) + if index < 0: + return 0 + if index > len(self): + return len(self) + return index + + def _slice_indices(self, index: slice) -> list[int]: + return list(range(*index.indices(len(self)))) + + def _copy_meta(self) -> dict[str, Any]: + return {name: self.meta[name] for name in self.meta} + + def _load_batch_lengths(self) -> list[int] | None: + try: + metadata = self.schunk.vlmeta[_BATCHSTORE_VLMETA_KEY] + except KeyError: + return None + batch_lengths = metadata.get("batch_lengths") + if not isinstance(batch_lengths, list): + return None + return [int(length) for length in batch_lengths] + + def _persist_batch_lengths(self) -> None: + if self._batch_lengths is None: + return + if len(self._batch_lengths) == 0: + if _BATCHSTORE_VLMETA_KEY in self.vlmeta: + del self.vlmeta[_BATCHSTORE_VLMETA_KEY] + return + self.schunk.vlmeta[_BATCHSTORE_VLMETA_KEY] = {"batch_lengths": list(self._batch_lengths)} + + def _get_batch_lengths(self) -> list[int] | None: + return self._batch_lengths + + def _ensure_batch_lengths(self) -> list[int]: + if self._batch_lengths is None: + self._batch_lengths = [] + return self._batch_lengths + + def _load_or_compute_batch_lengths(self) -> list[int]: + if self._batch_lengths is None: + self._batch_lengths = [len(self._get_batch(i)) for i in range(len(self))] + if self.mode != "r": + self._persist_batch_lengths() + return self._batch_lengths + + def _batch_length(self, index: int) -> int | None: + if self._batch_lengths is None: + return None + return self._batch_lengths[index] + + def _invalidate_item_cache(self) -> None: + self._item_prefix_sums = None + + def _get_item_prefix_sums(self) -> np.ndarray: + if self._item_prefix_sums is None: + batch_lengths = np.asarray(self._load_or_compute_batch_lengths(), dtype=np.int64) + prefix_sums = np.empty(len(batch_lengths) + 1, dtype=np.int64) + prefix_sums[0] = 0 + prefix_sums[1:] = np.cumsum(batch_lengths, dtype=np.int64) + self._item_prefix_sums = prefix_sums + return self._item_prefix_sums + + def _get_total_item_count(self) -> int: + return int(self._get_item_prefix_sums()[-1]) + + def _get_flat_item(self, index: int | slice) -> Any | list[Any]: + if isinstance(index, slice): + return [self._get_flat_item(i) for i in range(*index.indices(self._get_total_item_count()))] + if not isinstance(index, int): + raise TypeError("BatchStore item indices must be integers") + nitems = self._get_total_item_count() + if index < 0: + index += nitems + if index < 0 or index >= nitems: + raise IndexError("BatchStore item index out of range") + + prefix_sums = self._get_item_prefix_sums() + batch_index = int(np.searchsorted(prefix_sums, index, side="right") - 1) + item_index = int(index - prefix_sums[batch_index]) + return self[batch_index][item_index] + + def _block_sizes_from_batch_length(self, batch_length: int, nblocks: int) -> list[int]: + if self._max_blocksize is None or nblocks <= 0: + return [] + full_blocks, remainder = divmod(batch_length, self._max_blocksize) + block_sizes = [self._max_blocksize] * full_blocks + if remainder: + block_sizes.append(remainder) + if not block_sizes and batch_length > 0: + block_sizes.append(batch_length) + if len(block_sizes) != nblocks: + return [] + return block_sizes + + def _get_block_sizes(self, batch_sizes: list[int]) -> list[int] | None: + if self._max_blocksize is None: + return None + block_sizes: list[int] = [] + for index, batch_length in enumerate(batch_sizes): + lazychunk = self.schunk.get_lazychunk(index) + _, _, nblocks = blosc2.get_cbuffer_sizes(lazychunk) + sizes = self._block_sizes_from_batch_length(batch_length, nblocks) + if not sizes: + return None + block_sizes.extend(sizes) + return block_sizes + + def _total_nblocks(self) -> int: + total = 0 + for index in range(len(self)): + lazychunk = self.schunk.get_lazychunk(index) + _, _, nblocks = blosc2.get_cbuffer_sizes(lazychunk) + total += nblocks + return total + + def _user_vlmeta_items(self) -> dict[str, Any]: + return {key: value for key, value in self.vlmeta.getall().items() if key != _BATCHSTORE_VLMETA_KEY} + + def _normalize_msgpack_batch(self, value: object) -> list[Any]: + if isinstance(value, (str, bytes, bytearray, memoryview)): + raise TypeError("BatchStore entries must be sequences of Python objects") + if not isinstance(value, Sequence): + raise TypeError("BatchStore entries must be sequences of Python objects") + values = list(value) + if len(values) == 0: + raise ValueError("BatchStore entries cannot be empty") + return values + + def _normalize_arrow_batch(self, value: object): + pa, _ = self._require_pyarrow() + if isinstance(value, pa.ChunkedArray): + value = value.combine_chunks() + elif isinstance(value, pa.RecordBatch): + if value.num_columns != 1: + raise TypeError("Arrow RecordBatch inputs for BatchStore must have exactly one column") + value = value.column(0) + elif not isinstance(value, pa.Array): + if isinstance(value, (str, bytes, bytearray, memoryview)): + raise TypeError("BatchStore entries must be Arrow arrays or sequences of Python objects") + if not isinstance(value, Sequence): + raise TypeError("BatchStore entries must be Arrow arrays or sequences of Python objects") + value = pa.array(list(value)) + if len(value) == 0: + raise ValueError("BatchStore entries cannot be empty") + self._ensure_arrow_schema(value) + return value + + def _ensure_arrow_schema(self, batch) -> None: + if self._serializer != "arrow": + return + pa, _ = self._require_pyarrow() + schema = pa.schema([pa.field("values", batch.type)]) + if self._arrow_schema is None: + self._arrow_schema = schema.serialize().to_pybytes() + self._arrow_schema_obj = schema + return + existing_schema = self._get_arrow_schema() + if not existing_schema.equals(schema): + raise TypeError("All Arrow batches in a BatchStore must share the same schema") + + def _get_arrow_schema(self): + if self._serializer != "arrow": + return None + if self._arrow_schema is None: + raise RuntimeError("Arrow schema is not initialized") + if self._arrow_schema_obj is None: + pa, pa_ipc = self._require_pyarrow() + self._arrow_schema_obj = pa_ipc.read_schema(pa.BufferReader(self._arrow_schema)) + return self._arrow_schema_obj + + def _normalize_batch(self, value: object) -> Any: + if self._serializer == "arrow": + return self._normalize_arrow_batch(value) + return self._normalize_msgpack_batch(value) + + def _batch_len(self, batch: Any) -> int: + return len(batch) + + def _payload_sizes_for_batch(self, batch: Any) -> list[int]: + if self._serializer == "arrow": + total_size = batch.get_total_buffer_size() + avg_size = max(1, total_size // max(1, len(batch))) + return [avg_size] * len(batch) + return [len(msgpack_packb(item)) for item in batch] + + def _ensure_layout_for_batch(self, batch: Any) -> None: + layout_changed = False + if self._max_blocksize is None: + payload_sizes = self._payload_sizes_for_batch(batch) + self._max_blocksize = self._guess_blocksize(payload_sizes) + layout_changed = True + if self._serializer == "arrow" and self._arrow_schema is not None: + layout_changed = layout_changed or len(self) == 0 + if layout_changed: + self._persist_layout_metadata() + + def _persist_layout_metadata(self) -> None: + if len(self) > 0: + return + batch_lengths = None if self._batch_lengths is None else list(self._batch_lengths) + user_vlmeta = self._user_vlmeta_items() if len(self.vlmeta) > 0 else {} + storage = self._make_storage() + fixed_meta = dict(storage.meta or {}) + fixed_meta["batchstore"] = { + **dict(fixed_meta.get("batchstore", {})), + "max_blocksize": self._max_blocksize, + "serializer": self._serializer, + "arrow_schema": self._arrow_schema, + } + storage.meta = fixed_meta + schunk = blosc2.SChunk( + chunksize=-1, + data=None, + cparams=copy.deepcopy(self.cparams), + dparams=copy.deepcopy(self.dparams), + storage=storage, + ) + self._attach_schunk(schunk) + for key, value in user_vlmeta.items(): + self.vlmeta[key] = value + if batch_lengths is not None and self._batch_lengths is None: + self._batch_lengths = batch_lengths + + def _guess_blocksize(self, payload_sizes: list[int]) -> int: + if not payload_sizes: + raise ValueError("BatchStore entries cannot be empty") + clevel = self.cparams.clevel + if clevel == 9: + return len(payload_sizes) + if 0 < clevel < 5: + budget = blosc2.cpu_info.get("l1_data_cache_size") + elif 5 <= clevel < 9: + budget = blosc2.cpu_info.get("l2_cache_size") + else: + return len(payload_sizes) + if not isinstance(budget, int) or budget <= 0: + return len(payload_sizes) + total = 0 + count = 0 + for payload_size in payload_sizes: + if count > 0 and total + payload_size > budget: + break + total += payload_size + count += 1 + if count == 0: + count = 1 + return min(count, len(payload_sizes)) + + def _serialize_batch(self, value: object) -> Any: + batch = self._normalize_batch(value) + self._ensure_layout_for_batch(batch) + return batch + + def _serialize_msgpack_block(self, items: list[Any]) -> bytes: + payload = msgpack_packb(items) + _check_serialized_size(payload) + return payload + + def _serialize_arrow_block(self, items) -> bytes: + pa, _ = self._require_pyarrow() + batch = pa.record_batch([items], schema=self._get_arrow_schema()) + payload = batch.serialize().to_pybytes() + _check_serialized_size(payload) + return payload + + def _serialize_block(self, items: Any) -> bytes: + if self._serializer == "arrow": + return self._serialize_arrow_block(items) + return self._serialize_msgpack_block(items) + + def _deserialize_msgpack_block(self, payload: bytes) -> list[Any]: + return msgpack_unpackb(payload) + + def _deserialize_arrow_block(self, payload: bytes) -> list[Any]: + pa, pa_ipc = self._require_pyarrow() + batch = pa_ipc.read_record_batch(pa.BufferReader(payload), self._get_arrow_schema()) + return batch.column(0).to_pylist() + + def _deserialize_block(self, payload: bytes) -> list[Any]: + if self._serializer == "arrow": + return self._deserialize_arrow_block(payload) + return self._deserialize_msgpack_block(payload) + + def _vl_cparams_kwargs(self) -> dict[str, Any]: + return asdict(self.schunk.cparams) + + def _vl_dparams_kwargs(self) -> dict[str, Any]: + return asdict(self.schunk.dparams) + + def _compress_batch(self, batch: Any) -> bytes: + if self._max_blocksize is None: + raise RuntimeError("BatchStore max_blocksize is not initialized") + blocks = [ + self._serialize_block(batch[i : i + self._max_blocksize]) + for i in range(0, self._batch_len(batch), self._max_blocksize) + ] + return blosc2.blosc2_ext.vlcompress(blocks, **self._vl_cparams_kwargs()) + + def _decode_blocks(self, nbatch: int) -> list[list[Any]]: + block_payloads = blosc2.blosc2_ext.vldecompress( + self.schunk.get_chunk(nbatch), **self._vl_dparams_kwargs() + ) + return [self._deserialize_block(payload) for payload in block_payloads] + + def _get_batch(self, index: int) -> Batch: + return Batch(self, index, self.schunk.get_lazychunk(index)) + + def append(self, value: object) -> int: + """Append one batch and return the new number of batches.""" + self._check_writable() + batch = self._serialize_batch(value) + batch_payload = self._compress_batch(batch) + length = self._batch_len(batch) + new_len = self.schunk.append_chunk(batch_payload) + self._ensure_batch_lengths().append(length) + self._persist_batch_lengths() + self._invalidate_item_cache() + return new_len + + def insert(self, index: int, value: object) -> int: + """Insert one batch at ``index`` and return the new number of batches.""" + self._check_writable() + index = self._normalize_insert_index(index) + batch = self._serialize_batch(value) + batch_payload = self._compress_batch(batch) + length = self._batch_len(batch) + new_len = self.schunk.insert_chunk(index, batch_payload) + self._ensure_batch_lengths().insert(index, length) + self._persist_batch_lengths() + self._invalidate_item_cache() + return new_len + + def delete(self, index: int | slice) -> int: + """Delete the batch at ``index`` and return the new number of batches.""" + self._check_writable() + if isinstance(index, slice): + for idx in reversed(self._slice_indices(index)): + self.schunk.delete_chunk(idx) + if self._batch_lengths is not None: + del self._batch_lengths[idx] + self._persist_batch_lengths() + self._invalidate_item_cache() + return len(self) + index = self._normalize_index(index) + new_len = self.schunk.delete_chunk(index) + if self._batch_lengths is not None: + del self._batch_lengths[index] + self._persist_batch_lengths() + self._invalidate_item_cache() + return new_len + + def pop(self, index: int = -1) -> list[Any]: + """Remove and return the batch at ``index`` as a Python list.""" + self._check_writable() + if isinstance(index, slice): + raise NotImplementedError("Slicing is not supported for BatchStore") + index = self._normalize_index(index) + value = self[index][:] + self.delete(index) + return value + + def extend(self, values: object) -> None: + """Append all batches from an iterable of batches.""" + self._check_writable() + for value in values: + batch = self._serialize_batch(value) + batch_payload = self._compress_batch(batch) + self.schunk.append_chunk(batch_payload) + self._ensure_batch_lengths().append(self._batch_len(batch)) + self._persist_batch_lengths() + self._invalidate_item_cache() + + def clear(self) -> None: + """Remove all entries from the container.""" + self._check_writable() + storage = self._make_storage() + if storage.urlpath is not None: + blosc2.remove_urlpath(storage.urlpath) + schunk = blosc2.SChunk( + chunksize=-1, + data=None, + cparams=copy.deepcopy(self.cparams), + dparams=copy.deepcopy(self.dparams), + storage=storage, + ) + self._attach_schunk(schunk) + self._batch_lengths = [] + self._persist_batch_lengths() + self._invalidate_item_cache() + + def __getitem__(self, index: int | slice) -> Batch | list[Batch]: + """Return one batch or a list of batches.""" + if isinstance(index, slice): + return [self[i] for i in self._slice_indices(index)] + index = self._normalize_index(index) + return self._get_batch(index) + + def __setitem__(self, index: int | slice, value: object) -> None: + if isinstance(index, slice): + self._check_writable() + indices = self._slice_indices(index) + values = list(value) + step = 1 if index.step is None else index.step + if step == 1: + start = self._normalize_insert_index(0 if index.start is None else index.start) + for idx in reversed(indices): + self.schunk.delete_chunk(idx) + if self._batch_lengths is not None: + del self._batch_lengths[idx] + for offset, item in enumerate(values): + batch = self._serialize_batch(item) + batch_payload = self._compress_batch(batch) + self.schunk.insert_chunk(start + offset, batch_payload) + self._ensure_batch_lengths().insert(start + offset, self._batch_len(batch)) + self._persist_batch_lengths() + self._invalidate_item_cache() + return + if len(values) != len(indices): + raise ValueError( + f"attempt to assign sequence of size {len(values)} to extended slice of size {len(indices)}" + ) + for idx, item in zip(indices, values, strict=True): + batch = self._serialize_batch(item) + batch_payload = self._compress_batch(batch) + self.schunk.update_chunk(idx, batch_payload) + if self._batch_lengths is not None: + self._batch_lengths[idx] = self._batch_len(batch) + self._persist_batch_lengths() + self._invalidate_item_cache() + return + self._check_writable() + index = self._normalize_index(index) + batch = self._serialize_batch(value) + batch_payload = self._compress_batch(batch) + self.schunk.update_chunk(index, batch_payload) + if self._batch_lengths is not None: + self._batch_lengths[index] = self._batch_len(batch) + self._persist_batch_lengths() + self._invalidate_item_cache() + + def __delitem__(self, index: int | slice) -> None: + self.delete(index) + + def __len__(self) -> int: + """Return the number of batches stored in the container.""" + return self.schunk.nchunks + + def iter_items(self) -> Iterator[Any]: + """Iterate over all items across all batches in order.""" + for batch in self: + yield from batch + + def __iter__(self) -> Iterator[Batch]: + for i in range(len(self)): + yield self[i] + + @property + def meta(self): + return self.schunk.meta + + @property + def vlmeta(self): + return self.schunk.vlmeta + + @property + def cparams(self): + return self.schunk.cparams + + @property + def dparams(self): + return self.schunk.dparams + + @property + def max_blocksize(self) -> int | None: + return self._max_blocksize + + @property + def items(self) -> BatchStoreItems: + return self._items + + @property + def typesize(self) -> int: + return self.schunk.typesize + + @property + def nbytes(self) -> int: + return self.schunk.nbytes + + @property + def cbytes(self) -> int: + return self.schunk.cbytes + + @property + def cratio(self) -> float: + return self.schunk.cratio + + @property + def urlpath(self) -> str | None: + return self.schunk.urlpath + + @property + def contiguous(self) -> bool: + return self.schunk.contiguous + + @property + def info(self) -> InfoReporter: + """Return an info reporter with a compact summary of the store.""" + return InfoReporter(self) + + @property + def info_items(self) -> list: + """Return summary information as ``(name, value)`` pairs.""" + batch_sizes = self._get_batch_lengths() + if batch_sizes is None: + batch_sizes = [len(batch) for batch in self] + block_sizes = self._get_block_sizes(batch_sizes) + if batch_sizes: + batch_stats = ( + f"mean={statistics.fmean(batch_sizes):.2f}, max={max(batch_sizes)}, min={min(batch_sizes)}" + ) + nbatches_value = f"{len(self)} (items per batch: {batch_stats})" + else: + nbatches_value = f"{len(self)} (items per batch: n/a)" + if block_sizes: + block_stats = ( + f"mean={statistics.fmean(block_sizes):.2f}, max={max(block_sizes)}, min={min(block_sizes)}" + ) + nblocks_value = f"{self._total_nblocks()} (items per block: {block_stats})" + else: + nblocks_value = f"{self._total_nblocks()} (items per block: n/a)" + return [ + ("type", f"{self.__class__.__name__}"), + ("serializer", self.serializer), + ("nbatches", nbatches_value), + ("nblocks", nblocks_value), + ("nitems", sum(batch_sizes)), + ("nbytes", format_nbytes_info(self.nbytes)), + ("cbytes", format_nbytes_info(self.cbytes)), + ("cratio", f"{self.cratio:.2f}"), + ("cparams", self.cparams), + ("dparams", self.dparams), + ] + + def to_cframe(self) -> bytes: + """Serialize the full store to a Blosc2 cframe buffer.""" + return self.schunk.to_cframe() + + def copy(self, **kwargs: Any) -> BatchStore: + """Create a copy of the store with optional constructor overrides.""" + if "meta" in kwargs: + raise ValueError("meta should not be passed to copy") + kwargs["cparams"] = kwargs.get("cparams", copy.deepcopy(self.cparams)) + kwargs["dparams"] = kwargs.get("dparams", copy.deepcopy(self.dparams)) + kwargs["max_blocksize"] = kwargs.get("max_blocksize", self.max_blocksize) + kwargs["serializer"] = kwargs.get("serializer", self.serializer) + user_vlmeta = self._user_vlmeta_items() if len(self.vlmeta) > 0 else {} + + if "storage" in kwargs: + storage = self._coerce_storage(kwargs["storage"], {}) + fixed_meta = self._copy_meta() + if storage.meta is not None: + fixed_meta.update(storage.meta) + storage.meta = fixed_meta + kwargs["storage"] = storage + else: + kwargs["meta"] = self._copy_meta() + kwargs["contiguous"] = kwargs.get("contiguous", self.schunk.contiguous) + if "urlpath" in kwargs and "mode" not in kwargs: + kwargs["mode"] = "w" + + out = BatchStore(**kwargs) + for key, value in user_vlmeta.items(): + out.vlmeta[key] = value + out.extend(self) + return out + + def __enter__(self) -> BatchStore: + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + return False + + def __repr__(self) -> str: + return f"BatchStore(len={len(self)}, urlpath={self.urlpath!r})" + + @property + def serializer(self) -> str: + """Serializer name used for batch payloads.""" + return self._serializer diff --git a/src/blosc2/blosc2_ext.pyx b/src/blosc2/blosc2_ext.pyx index c56104ab..8a442eb7 100644 --- a/src/blosc2/blosc2_ext.pyx +++ b/src/blosc2/blosc2_ext.pyx @@ -8,6 +8,7 @@ #cython: language_level=3 import os +import dataclasses import ast import atexit import pathlib @@ -42,7 +43,6 @@ cimport numpy as np np.import_array() - cdef extern from "": ctypedef signed char int8_t ctypedef signed short int16_t @@ -53,6 +53,12 @@ cdef extern from "": ctypedef unsigned int uint32_t ctypedef unsigned long long uint64_t +ctypedef fused T: + float + double + int32_t + int64_t + cdef extern from "": int printf(const char *format, ...) nogil @@ -279,9 +285,17 @@ cdef extern from "blosc2.h": blosc2_context * context, const void * src, int32_t srcsize, void * dest, int32_t destsize) nogil + int blosc2_vlcompress_ctx( + blosc2_context * context, const void * const * srcs, const int32_t * srcsizes, + int32_t nblocks, void * dest, int32_t destsize) nogil + int blosc2_decompress_ctx(blosc2_context * context, const void * src, int32_t srcsize, void * dest, int32_t destsize) nogil + int blosc2_vldecompress_ctx(blosc2_context* context, const void* src, + int32_t srcsize, void** dests, + int32_t* destsizes, int32_t maxblocks) + int blosc2_getitem_ctx(blosc2_context* context, const void* src, int32_t srcsize, int start, int nitems, void* dest, int32_t destsize) nogil @@ -381,6 +395,8 @@ cdef extern from "blosc2.h": c_bool *needs_free) nogil int blosc2_schunk_get_lazychunk(blosc2_schunk *schunk, int64_t nchunk, uint8_t ** chunk, c_bool *needs_free) nogil + int blosc2_schunk_get_vlblock(blosc2_schunk *schunk, int64_t nchunk, int32_t nblock, + uint8_t **dest, int32_t *destsize) int blosc2_schunk_get_slice_buffer(blosc2_schunk *schunk, int64_t start, int64_t stop, void *buffer) int blosc2_schunk_set_slice_buffer(blosc2_schunk *schunk, int64_t start, int64_t stop, void *buffer) int blosc2_schunk_get_cparams(blosc2_schunk *schunk, blosc2_cparams** cparams) @@ -407,6 +423,9 @@ cdef extern from "blosc2.h": uint8_t **content, int32_t *content_len) int blosc2_vlmeta_delete(blosc2_schunk *schunk, const char *name) int blosc2_vlmeta_get_names(blosc2_schunk *schunk, char **names) + int blosc2_vldecompress_block_ctx(blosc2_context* context, const void* src, + int32_t srcsize, int32_t nblock, uint8_t** dest, + int32_t* destsize) int blosc1_get_blocksize() @@ -669,6 +688,13 @@ ctypedef struct me_udata: int64_t blocks_in_chunk[B2ND_MAX_DIM] me_expr* miniexpr_handle +ctypedef struct mm_udata: + b2nd_array_t** inputs + b2nd_array_t* array + int64_t chunks_strides[3][B2ND_MAX_DIM] + int64_t blocks_strides[3][B2ND_MAX_DIM] + int64_t el_strides[3][B2ND_MAX_DIM] + MAX_TYPESIZE = BLOSC2_MAXTYPESIZE MAX_BUFFERSIZE = BLOSC2_MAX_BUFFERSIZE MAX_BLOCKSIZE = BLOSC2_MAXBLOCKSIZE @@ -982,7 +1008,7 @@ cdef _check_cparams(blosc2_cparams *cparams): if ufilters[i] and cparams.filters[i] in blosc2.ufilters_registry.keys(): raise ValueError("Cannot use multi-threading with user defined Python filters") - if cparams.prefilter != NULL and cparams.prefilter != miniexpr_prefilter: + if cparams.prefilter != NULL and (cparams.prefilter != miniexpr_prefilter and cparams.prefilter != matmul_prefilter): # Note: miniexpr_prefilter uses miniexpr C API which is thread-friendly, raise ValueError("`nthreads` must be 1 when a prefilter is set") @@ -1095,6 +1121,7 @@ def compress2(src, **kwargs): return dest[:size] cdef create_dparams_from_kwargs(blosc2_dparams *dparams, kwargs, blosc2_cparams* cparams=NULL): + memcpy(dparams, &BLOSC2_DPARAMS_DEFAULTS, sizeof(BLOSC2_DPARAMS_DEFAULTS)) dparams.nthreads = kwargs.get('nthreads', blosc2.nthreads) dparams.schunk = NULL dparams.postfilter = NULL @@ -1154,6 +1181,158 @@ def decompress2(src, dst=None, **kwargs): raise ValueError("Error while decompressing, check the src data and/or the dparams") +def vlcompress(srcs, **kwargs): + cdef blosc2_cparams cparams + create_cparams_from_kwargs(&cparams, kwargs) + + cdef Py_ssize_t nblocks = len(srcs) + if nblocks <= 0: + raise ValueError("At least one block is required") + + cdef blosc2_context *cctx = NULL + cdef Py_buffer *buffers = calloc(nblocks, sizeof(Py_buffer)) + cdef const void **src_ptrs = malloc(nblocks * sizeof(void *)) + cdef int32_t *srcsizes = malloc(nblocks * sizeof(int32_t)) + cdef Py_ssize_t acquired = 0 + cdef Py_ssize_t i + cdef int64_t total_nbytes = 0 + cdef int32_t len_dest + cdef int size + cdef Py_ssize_t release_i + cdef void *_dest + if buffers == NULL or src_ptrs == NULL or srcsizes == NULL: + free(buffers) + free(src_ptrs) + free(srcsizes) + raise MemoryError() + + try: + for i in range(nblocks): + PyObject_GetBuffer(srcs[i], &buffers[i], PyBUF_SIMPLE) + acquired += 1 + if buffers[i].len <= 0: + raise ValueError("Each VL block must have at least one byte") + src_ptrs[i] = buffers[i].buf + srcsizes[i] = buffers[i].len + total_nbytes += buffers[i].len + + # VL blocks can carry enough per-block framing that the simple + # total_nbytes + global_overhead estimate is too small for many tiny + # buffers. Budget one max-overhead chunk per block as a conservative + # upper bound for the temporary destination. + len_dest = (total_nbytes + BLOSC2_MAX_OVERHEAD * (nblocks + 1) + 64) + dest = PyBytes_FromStringAndSize(NULL, len_dest) + if dest is None: + raise MemoryError() + _dest = dest + cctx = blosc2_create_cctx(cparams) + if cctx == NULL: + raise RuntimeError("Could not create the compression context") + if RELEASEGIL: + with nogil: + size = blosc2_vlcompress_ctx(cctx, src_ptrs, srcsizes, nblocks, _dest, len_dest) + else: + size = blosc2_vlcompress_ctx(cctx, src_ptrs, srcsizes, nblocks, _dest, len_dest) + finally: + if cctx != NULL: + blosc2_free_ctx(cctx) + for release_i in range(acquired): + PyBuffer_Release(&buffers[release_i]) + free(buffers) + free(src_ptrs) + free(srcsizes) + + if size < 0: + raise RuntimeError("Could not compress the data") + elif size == 0: + del dest + raise RuntimeError("The result could not fit ") + return dest[:size] + + +def vldecompress(src, **kwargs): + cdef blosc2_dparams dparams + create_dparams_from_kwargs(&dparams, kwargs) + + cdef blosc2_context *dctx = blosc2_create_dctx(dparams) + if dctx == NULL: + raise RuntimeError("Could not create decompression context") + + cdef const uint8_t[:] typed_view_src + mem_view_src = memoryview(src) + typed_view_src = mem_view_src.cast('B') + _check_comp_length('src', typed_view_src.nbytes) + cdef int32_t nbytes + cdef int32_t cbytes + cdef int32_t nblocks + blosc2_cbuffer_sizes(&typed_view_src[0], &nbytes, &cbytes, &nblocks) + if nblocks <= 0: + blosc2_free_ctx(dctx) + raise ValueError("Chunk does not contain VL blocks") + + cdef void **dests = calloc(nblocks, sizeof(void *)) + cdef int32_t *destsizes = malloc(nblocks * sizeof(int32_t)) + cdef int32_t rc + cdef int32_t i + cdef list out = [] + if dests == NULL or destsizes == NULL: + blosc2_free_ctx(dctx) + free(dests) + free(destsizes) + raise MemoryError() + + try: + rc = blosc2_vldecompress_ctx(dctx, &typed_view_src[0], cbytes, dests, destsizes, nblocks) + if rc < 0: + raise RuntimeError("Could not decompress the data") + for i in range(rc): + out.append(PyBytes_FromStringAndSize(dests[i], destsizes[i])) + free(dests[i]) + dests[i] = NULL + return out + finally: + for i in range(nblocks): + if dests[i] != NULL: + free(dests[i]) + free(dests) + free(destsizes) + blosc2_free_ctx(dctx) + + +def vldecompress_block(src, int32_t nblock, **kwargs): + cdef blosc2_dparams dparams + create_dparams_from_kwargs(&dparams, kwargs) + + cdef blosc2_context *dctx = blosc2_create_dctx(dparams) + if dctx == NULL: + raise RuntimeError("Could not create decompression context") + + cdef const uint8_t[:] typed_view_src + mem_view_src = memoryview(src) + typed_view_src = mem_view_src.cast('B') + _check_comp_length('src', typed_view_src.nbytes) + + cdef uint8_t *dest = NULL + cdef int32_t destsize = 0 + cdef int32_t rc + try: + rc = blosc2_vldecompress_block_ctx( + dctx, + &typed_view_src[0], + typed_view_src.nbytes, + nblock, + &dest, + &destsize, + ) + if rc < 0: + raise RuntimeError("Could not decompress the block") + return PyBytes_FromStringAndSize(dest, destsize) + finally: + if dest != NULL: + free(dest) + blosc2_free_ctx(dctx) + + cdef create_storage(blosc2_storage *storage, kwargs): contiguous = kwargs.get('contiguous', blosc2.storage_dflts['contiguous']) storage.contiguous = contiguous @@ -1559,12 +1738,32 @@ cdef class SChunk: free(chunk) return ret_chunk + def get_vlblock(self, nchunk, nblock): + cdef uint8_t *block + cdef int32_t destsize + cbytes = blosc2_schunk_get_vlblock(self.schunk, nchunk, nblock, &block, &destsize) + if cbytes < 0: + raise RuntimeError("Error while getting the vlblock") + ret_block = PyBytes_FromStringAndSize(block, destsize) + free(block) + return ret_block + def delete_chunk(self, nchunk): rc = blosc2_schunk_delete_chunk(self.schunk, nchunk) if rc < 0: raise RuntimeError("Could not delete the desired chunk") return rc + def append_chunk(self, chunk): + cdef const uint8_t[:] typed_view_chunk + mem_view_chunk = memoryview(chunk) + typed_view_chunk = mem_view_chunk.cast('B') + _check_comp_length('chunk', len(typed_view_chunk)) + rc = blosc2_schunk_append_chunk(self.schunk, &typed_view_chunk[0], True) + if rc < 0: + raise RuntimeError("Could not append the desired chunk") + return rc + def insert_chunk(self, nchunk, chunk): cdef const uint8_t[:] typed_view_chunk mem_view_chunk = memoryview(chunk) @@ -1903,6 +2102,7 @@ cdef class SChunk: cpdef remove_prefilter(self, func_name, _new_ctx=True): cdef udf_udata* udf_data cdef user_filters_udata* udata + cdef mm_udata* mm_data if func_name is not None and func_name in blosc2.prefilter_funcs: del blosc2.prefilter_funcs[func_name] @@ -1924,6 +2124,13 @@ cdef class SChunk: if me_data.eval_params != NULL: free(me_data.eval_params) free(me_data) + elif self.schunk.storage.cparams.prefilter == matmul_prefilter: + if self.schunk.storage.cparams.preparams != NULL: + mm_data = self.schunk.storage.cparams.preparams.user_data + if mm_data != NULL: + if mm_data.inputs != NULL: + free(mm_data.inputs) + free(mm_data) elif self.schunk.storage.cparams.prefilter != NULL: # From Python the preparams->udata with always have the field py_func if self.schunk.storage.cparams.preparams != NULL: @@ -2017,7 +2224,6 @@ cdef int aux_miniexpr(me_udata *udata, int64_t nchunk, int32_t nblock, cdef b2nd_array_t* ndarr cdef int rc cdef void** input_buffers = malloc(udata.ninputs * sizeof(uint8_t*)) - cdef float *buf cdef uint8_t* src cdef uint8_t* chunk cdef c_bool needs_free @@ -2143,6 +2349,174 @@ cdef int aux_miniexpr(me_udata *udata, int64_t nchunk, int32_t nblock, return 0 +cdef int matmul_block_kernel(T* A, T* B, T* C, int M, int K, int N) nogil: + cdef int r, c, k + cdef T a + cdef int rowA, rowC, rowB + for r in range(M): + rowA = r * K + rowC = r * N + for k in range(K): + a = A[rowA + k] + rowB = k * N + for c in range(N): + C[rowC + c] += (a * B[rowB + c]) + return 0 + +cdef int aux_matmul(mm_udata *udata, int64_t nchunk, int32_t nblock, void *params_output, int32_t typesize, int typecode) nogil: + # Declare all C variables at the beginning + cdef b2nd_array_t* out_arr + cdef b2nd_array_t* ndarr + cdef c_bool first_run + cdef int rc, p, q, r + cdef void** input_buffers = malloc(2 * sizeof(uint8_t*)) + cdef uint8_t** src = malloc(2 * sizeof(uint8_t*)) + cdef int32_t chunk_nbytes[2] + cdef int32_t chunk_cbytes[2] + cdef int32_t block_nbytes[2] + cdef int blocknitems[2] + cdef int startA, startB, expected_blocknitems + cdef blosc2_context* dctx + cdef int i, j, block_i, block_j, chunk_i, chunk_j, ncols, block_ncols, Bblock_ncols, Bncols, Ablock_ncols, Ancols + cdef int nchunkA = 0, nchunkB = 0, nblockA = 0, nblockB = 0, offsetA = 0, offsetB = 0, offset = 0 + out_arr = udata.array + cdef int ndim = out_arr.ndim + cdef int nchunk_ = nchunk + cdef int coord, batch, batch_, batches = 1 + cdef int out_chunk_nrows, out_chunk_ncols, out_block_nrows, out_block_ncols + + # batches = sum(strides[i]*elcoords[i]) + for i in range(ndim - 2): + batches *= out_arr.blockshape[i] + + # nchunk = sum(strides[i]*chunkcoords[i]) + for i in range(ndim - 2): + coord = nchunk_ // udata.chunks_strides[0][i] + nchunk_ = nchunk_ % udata.chunks_strides[0][i] + nchunkA += coord * udata.chunks_strides[1][i] + nchunkB += coord * udata.chunks_strides[2][i] + + ncols = udata.chunks_strides[0][ndim - 2] + Ancols = udata.chunks_strides[1][ndim - 2] + Bncols = udata.chunks_strides[2][ndim - 2] + out_chunk_nrows = out_arr.chunkshape[ndim - 2] + out_chunk_ncols = out_arr.chunkshape[ndim - 1] + + # nblock = sum(strides[i]*blockcoords[i]) + cdef int nblock_ = nblock + for i in range(ndim - 2): + coord = nblock_ // udata.blocks_strides[0][i] + nblock_ = nblock_ % udata.blocks_strides[0][i] + nblockA += coord * udata.blocks_strides[1][i] + nblockB += coord * udata.blocks_strides[2][i] + + block_ncols = udata.blocks_strides[0][ndim - 2] + Ablock_ncols = udata.blocks_strides[1][ndim - 2] + Bblock_ncols = udata.blocks_strides[2][ndim - 2] + out_block_nrows = out_arr.blockshape[ndim - 2] + out_block_ncols = out_arr.blockshape[ndim - 1] + + memset(params_output, 0, out_arr.blocknitems * typesize) + + dctx = blosc2_create_dctx(BLOSC2_DPARAMS_DEFAULTS) + + first_run = True + while True: # chunk loop + for i in range(2): + chunk_idx = nchunkA if i == 0 else nchunkB + ndarr = udata.inputs[i] + ndim = ndarr.ndim + src[i] = ndarr.sc.data[chunk_idx] + rc = blosc2_cbuffer_sizes(src[i], &chunk_nbytes[i], &chunk_cbytes[i], &block_nbytes[i]) + if rc < 0: + raise ValueError("miniexpr: error getting cbuffer sizes") + if block_nbytes[i] <= 0: + raise ValueError("miniexpr: invalid block size") + if first_run: + if i == 0: + q = ndarr.blockshape[ndim - 1] + p = ndarr.blockshape[ndim - 2] + # nchunk_ = chunks_in_row * chunk_row + chunk_col + # convert from chunk_idx to element idx chunk_i (row) + chunk_i = nchunk_ // ncols * out_chunk_nrows + chunk_startA = nchunkA + chunk_i // ndarr.chunkshape[ndim - 2] * Ancols + nchunkA = chunk_startA + # nblock_ = blocks_in_chunkrow * block_row + block_col + # convert from block_idx to element idx block_i (row) + block_i = nblock_ // block_ncols * out_block_nrows + block_startA = nblockA + block_i // p * Ablock_ncols + else: # i = 1 + r = ndarr.blockshape[ndim - 1] + # convert from chunk_idx to element idx chunk_j (col) + chunk_j = nchunk_ % ncols * out_chunk_ncols + chunk_startB = nchunkB + chunk_j // ndarr.chunkshape[ndim - 1] + nchunkB = chunk_startB + # convert from block_idx to element idx block_j (col) + block_j = nblock_ % block_ncols * out_block_ncols + block_startB = nblockB + block_j // r + input_buffers[i] = malloc(block_nbytes[i]) + if input_buffers[i] == NULL: + raise MemoryError("miniexpr: cannot allocate input block buffer") + blocknitems[i] = block_nbytes[i] // ndarr.sc.typesize + + first_run = False + nblockA = block_startA + nblockB = block_startB + while True: # block loop + startA = nblockA * blocknitems[0] + startB = nblockB * blocknitems[1] + rc = blosc2_getitem_ctx(dctx, src[0], chunk_cbytes[0], startA, blocknitems[0], + input_buffers[0], block_nbytes[0]) + if rc < 0: + raise ValueError("matmul: error decompressing the A chunk") + rc = blosc2_getitem_ctx(dctx, src[1], chunk_cbytes[1], startB, blocknitems[1], + input_buffers[1], block_nbytes[1]) + if rc < 0: + raise ValueError("matmul: error decompressing the B chunk") + batch = 0 + while batch < batches: + batch_ = batch + offsetA = 0 + offsetB = 0 + offset = 0 + for i in range(ndim - 2): + coord = batch_ // udata.el_strides[0][i] + batch_ = batch_ % udata.el_strides[0][i] + offsetA += coord * udata.el_strides[1][i] + offsetB += coord * udata.el_strides[2][i] + offset += coord * udata.el_strides[0][i] + if typecode == 0: + if typesize == 4: + rc = matmul_block_kernel[float](input_buffers[0] + offsetA, input_buffers[1] + offsetB, params_output + offset, p, q, r) + else: + rc = matmul_block_kernel[double](input_buffers[0] + offsetA, input_buffers[1] + offsetB, params_output + offset, p, q, r) + elif typecode == 1: + if typesize == 4: + rc = matmul_block_kernel[int32_t](input_buffers[0] + offsetA, input_buffers[1] + offsetB, params_output + offset, p, q, r) + else: + rc = matmul_block_kernel[int64_t](input_buffers[0] + offsetA, input_buffers[1] + offsetB, params_output + offset, p, q, r) + else: + with gil: + raise ValueError("Unsupported dtype") + batch += 1 + nblockA += 1 + nblockB += Bblock_ncols + if (nblockA % Ablock_ncols == 0): + break + nchunkA += 1 + nchunkB += Bncols + if (nchunkA % Ancols == 0): + break + + + blosc2_free_ctx(dctx) + # Free resources + for i in range(2): + free(input_buffers[i]) + free(input_buffers) + free(src) + + return 0 # Aux function for prefilter and postfilter udf cdef int aux_udf(udf_udata *udata, int64_t nchunk, int32_t nblock, @@ -2221,6 +2595,19 @@ cdef int miniexpr_prefilter(blosc2_prefilter_params *params): return aux_miniexpr( params.user_data, params.nchunk, params.nblock, False, params.output, params.output_typesize) +cdef int matmul_prefilter(blosc2_prefilter_params *params): + cdef int typecode + + cdef mm_udata* udata = params.user_data + cdef b2nd_array_t* out_arr = udata.array + cdef char dtype_kind = out_arr.dtype[1] + if dtype_kind == 'f': + typecode = 0 + elif dtype_kind == 'i': + typecode = 1 + else: + raise ValueError("Unsupported dtype") + return aux_matmul(udata, params.nchunk, params.nblock, params.output, params.output_typesize, typecode) cdef int general_udf_prefilter(blosc2_prefilter_params *params): cdef udf_udata *udata = params.user_data @@ -2434,7 +2821,8 @@ def open(urlpath, mode, offset, **kwargs): if mode != "w" and kwargs is not None: check_schunk_params(schunk, kwargs) cparams = kwargs.get("cparams") - # For reading with the default number of threads + # nthreads is not stored in the frame; apply the live global when the caller + # did not supply an explicit cparams — symmetric with the DParams default below. dparams = kwargs.get("dparams", blosc2.DParams()) if is_ndarray: @@ -2442,6 +2830,8 @@ def open(urlpath, mode, offset, **kwargs): _array=PyCapsule_New(array, "b2nd_array_t*", NULL)) if cparams is not None: res.schunk.cparams = cparams if isinstance(cparams, blosc2.CParams) else blosc2.CParams(**cparams) + else: + res.schunk.cparams = dataclasses.replace(res.schunk.cparams, nthreads=blosc2.nthreads) if dparams is not None: res.schunk.dparams = dparams if isinstance(dparams, blosc2.DParams) else blosc2.DParams(**dparams) res.schunk.mode = mode @@ -2450,6 +2840,8 @@ def open(urlpath, mode, offset, **kwargs): mode=mode, **kwargs) if cparams is not None: res.cparams = cparams if isinstance(cparams, blosc2.CParams) else blosc2.CParams(**cparams) + else: + res.cparams = dataclasses.replace(res.cparams, nthreads=blosc2.nthreads) if dparams is not None: res.dparams = dparams if isinstance(dparams, blosc2.DParams) else blosc2.DParams(**dparams) @@ -3068,6 +3460,49 @@ cdef class NDArray: return udata + cdef mm_udata *_fill_mm_udata(self, inputs): + cdef mm_udata *udata = malloc(sizeof(mm_udata)) + cdef int cstrides, bstrides, estrides + cdef b2nd_array_t* inp + cdef b2nd_array_t** inputs_ = malloc(2 * sizeof(b2nd_array_t*)) + for i in range(2): + operand = inputs['x1'] if i == 0 else inputs['x2'] + inputs_[i] = operand.c_array + inputs_[i].chunk_cache.nchunk = -1 + inputs_[i].chunk_cache.data = NULL + udata.inputs = inputs_ + udata.array = self.array + + # Save these in udf_udata to avoid computing them for each block + for i in range(3): + udata.chunks_strides[i][self.array.ndim - 1] = 1 + udata.blocks_strides[i][self.array.ndim - 1] = 1 + udata.el_strides[i][self.array.ndim - 1] = 1 + for idx in range(2, self.array.ndim + 1): + i = self.array.ndim - idx + udata.chunks_strides[0][i] = udata.chunks_strides[0][i + 1] * udata.array.extshape[i + 1] // udata.array.chunkshape[i + 1] + udata.blocks_strides[0][i] = udata.blocks_strides[0][i + 1] * udata.array.extchunkshape[i + 1] // udata.array.blockshape[i + 1] + udata.el_strides[0][i] = udata.el_strides[0][i + 1] * udata.array.blockshape[i + 1] + + for j in range(1, 3): + inp = inputs_[j - 1] + cstrides = bstrides = estrides = 1 + for idx in range(2, self.array.ndim + 1): + i = inp.ndim - idx + if (inp.shape[i + 1] == 1 and i < inp.ndim - 3) or i < 0: + udata.chunks_strides[j][i] = 0 + udata.blocks_strides[j][i] = 0 + udata.el_strides[j][i] = 0 + else: + bstrides *= inp.extchunkshape[i + 1] // inp.blockshape[i + 1] + cstrides *= inp.extshape[i + 1] // inp.chunkshape[i + 1] + estrides *= inp.blockshape[i + 1] + udata.chunks_strides[j][i] = cstrides + udata.blocks_strides[j][i] = bstrides + udata.el_strides[j][i] = estrides + + return udata + def _set_pref_expr(self, expression, inputs, fp_accuracy, aux_reduc=None, jit=None): # Set prefilter for miniexpr cdef blosc2_cparams* cparams = self.array.sc.storage.cparams @@ -3153,6 +3588,26 @@ cdef class NDArray: if self.array.sc.cctx == NULL: raise RuntimeError("Could not create compression context") + def _set_pref_matmul(self, inputs, fp_accuracy): + # Set prefilter for miniexpr + cdef blosc2_cparams* cparams = self.array.sc.storage.cparams + cparams.prefilter = matmul_prefilter + + cdef mm_udata* udata = self._fill_mm_udata(inputs) + cdef b2nd_array_t* out_arr = udata.array + cdef blosc2_prefilter_params* preparams = calloc(1, sizeof(blosc2_prefilter_params)) + preparams.user_data = udata + preparams.output_is_disposable = False + cparams.preparams = preparams + _check_cparams(cparams) + + if self.array.sc.cctx != NULL: + # Freeing NULL context can lead to segmentation fault + blosc2_free_ctx(self.array.sc.cctx) + self.array.sc.cctx = blosc2_create_cctx(dereference(cparams)) + if self.array.sc.cctx == NULL: + raise RuntimeError("Could not create compression context") + def _set_pref_udf(self, func, inputs_id): if self.array.sc.storage.cparams.nthreads > 1: raise AttributeError("compress `nthreads` must be 1 when assigning a prefilter") diff --git a/src/blosc2/c2array.py b/src/blosc2/c2array.py index e8556ba4..11f7f6cb 100644 --- a/src/blosc2/c2array.py +++ b/src/blosc2/c2array.py @@ -18,7 +18,7 @@ import requests import blosc2 -from blosc2.info import InfoReporter +from blosc2.info import InfoReporter, format_nbytes_info _subscriber_data = { "urlbase": os.environ.get("BLOSC_C2URLBASE"), @@ -424,8 +424,8 @@ def info_items(self) -> list: items += [("chunks", self.chunks)] items += [("blocks", self.blocks)] items += [("dtype", self.dtype)] - items += [("nbytes", self.nbytes)] - items += [("cbytes", self.cbytes)] + items += [("nbytes", format_nbytes_info(self.nbytes))] + items += [("cbytes", format_nbytes_info(self.cbytes))] items += [("cratio", f"{self.cratio:.2f}")] items += [("cparams", self.cparams)] # items += [("dparams", self.dparams)] diff --git a/src/blosc2/core.py b/src/blosc2/core.py index 4ec139c4..d574a21e 100644 --- a/src/blosc2/core.py +++ b/src/blosc2/core.py @@ -1918,8 +1918,9 @@ def ndarray_from_cframe(cframe: bytes | str, copy: bool = False) -> blosc2.NDArr def from_cframe( cframe: bytes | str, copy: bool = True -) -> blosc2.EmbedStore | blosc2.NDArray | blosc2.SChunk: - """Create a :ref:`EmbedStore `, :ref:`NDArray ` or :ref:`SChunk ` instance +) -> blosc2.EmbedStore | blosc2.NDArray | blosc2.SChunk | blosc2.BatchStore | blosc2.VLArray: + """Create a :ref:`EmbedStore `, :ref:`NDArray `, :ref:`SChunk `, + :ref:`BatchStore ` or :ref:`VLArray ` instance from a contiguous frame buffer. Parameters @@ -1936,7 +1937,8 @@ def from_cframe( Returns ------- - out: :ref:`EmbedStore `, :ref:`NDArray ` or :ref:`SChunk ` + out: :ref:`EmbedStore `, :ref:`NDArray `, :ref:`SChunk `, + :ref:`BatchStore ` or :ref:`VLArray ` A new instance of the appropriate type containing the data passed. See Also @@ -1950,6 +1952,10 @@ def from_cframe( # Check the metalayer to determine the type if "b2embed" in schunk.meta: return blosc2.estore_from_cframe(cframe, copy=copy) + if "batchstore" in schunk.meta: + return blosc2.BatchStore(_from_schunk=schunk_from_cframe(cframe, copy=copy)) + if "vlarray" in schunk.meta: + return blosc2.vlarray_from_cframe(cframe, copy=copy) if "b2nd" in schunk.meta: return ndarray_from_cframe(cframe, copy=copy) return schunk_from_cframe(cframe, copy=copy) diff --git a/src/blosc2/dict_store.py b/src/blosc2/dict_store.py index 9ac3cf46..6fe9e7ee 100644 --- a/src/blosc2/dict_store.py +++ b/src/blosc2/dict_store.py @@ -5,19 +5,24 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### +from __future__ import annotations + import os import shutil import tempfile +import warnings import zipfile -from collections.abc import Iterator, Set -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np import blosc2 from blosc2.c2array import C2Array from blosc2.embed_store import EmbedStore -from blosc2.schunk import SChunk +from blosc2.schunk import SChunk, _process_opened_object + +if TYPE_CHECKING: + from collections.abc import Iterator, Set class DictStore: @@ -32,6 +37,8 @@ class DictStore: are stored as .b2nd files. - blosc2.SChunk: super-chunks. When persisted externally they are stored as .b2f files. + - blosc2.BatchStore: batched variable-length containers. When persisted + externally they are stored as .b2b files. - blosc2.C2Array: columnar containers. These are always kept inside the embedded store (never externalized). - numpy.ndarray: converted to blosc2.NDArray on assignment. @@ -78,7 +85,7 @@ class DictStore: >>> schunk.append_data(b"abcd") 4 >>> dstore["/dir1/schunk1"] = schunk # externalized as .b2f if above threshold - >>> dstore.to_b2z() # persist to the zip file; external files are copied in + >>> dstore.to_b2z(filename="my_dstore.b2z") # persist to the zip file; external files are copied in >>> print(sorted(dstore.keys())) ['/dir1/node3', '/dir1/schunk1', '/node1', '/node2'] >>> print(dstore["/node1"][:])) @@ -87,7 +94,10 @@ class DictStore: Notes ----- - External persistence uses the following file extensions: - .b2nd for NDArray and .b2f for SChunk. + .b2nd for NDArray, .b2f for SChunk, and .b2b for BatchStore. + These suffixes are a naming convention for newly written leaves; when + reopening an existing store, leaf typing is resolved from object + metadata instead of trusting the suffix alone. """ def __init__( @@ -106,7 +116,7 @@ def __init__( """ See :class:`DictStore` for full documentation of parameters. """ - self.localpath = localpath if isinstance(localpath, (str, bytes)) else str(localpath) + self.localpath = localpath if isinstance(localpath, str | bytes) else str(localpath) if not self.localpath.endswith((".b2z", ".b2d")): raise ValueError(f"localpath must have a .b2z or .b2d extension; you passed: {self.localpath}") if mode not in ("r", "w", "a"): @@ -176,10 +186,7 @@ def _init_read_mode(self, dparams: blosc2.DParams | None = None): mmap_mode=self.mmap_mode, dparams=dparams, ) - for filepath in self.offsets: - if filepath.endswith((".b2nd", ".b2f")): - key = "/" + filepath[: -5 if filepath.endswith(".b2nd") else -4] - self.map_tree[key] = filepath + self._update_map_tree_from_offsets() else: # .b2d if not os.path.isdir(self.localpath): raise FileNotFoundError(f"Directory {self.localpath} does not exist for reading.") @@ -195,6 +202,90 @@ def _init_read_mode(self, dparams: blosc2.DParams | None = None): self._estore = EmbedStore(_from_schunk=schunk) self.storage.meta = self._estore.storage.meta + @staticmethod + def _logical_key_from_relpath(rel_path: str) -> str: + """Map an external leaf path to its logical tree key.""" + rel_path = rel_path.replace(os.sep, "/") + key = os.path.splitext(rel_path)[0] + if not key.startswith("/"): + key = "/" + key + return key + + @staticmethod + def _expected_ext_from_kind(kind: str) -> str: + """Return the canonical write-time suffix for a supported external leaf kind.""" + if kind == "ndarray": + return ".b2nd" + if kind == "batchstore": + return ".b2b" + return ".b2f" + + @classmethod + def _opened_external_kind( + cls, + opened: blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore | C2Array | Any, + rel_path: str, + ) -> str | None: + """Return the supported external leaf kind for an already opened object.""" + processed = _process_opened_object(opened) + if isinstance(processed, blosc2.BatchStore): + kind = "batchstore" + elif isinstance(processed, blosc2.VLArray): + kind = "vlarray" + elif isinstance(processed, blosc2.NDArray): + kind = "ndarray" + elif isinstance(processed, SChunk): + kind = "schunk" + else: + warnings.warn( + f"Ignoring unsupported Blosc2 object at '{rel_path}' during DictStore discovery: " + f"{type(processed).__name__}", + UserWarning, + stacklevel=2, + ) + return None + + expected_ext = cls._expected_ext_from_kind(kind) + found_ext = os.path.splitext(rel_path)[1] + if found_ext != expected_ext: + warnings.warn( + f"External leaf '{rel_path}' uses extension '{found_ext}' but metadata resolves to " + f"{type(processed).__name__}; expected '{expected_ext}'.", + UserWarning, + stacklevel=2, + ) + return kind + + def _probe_external_leaf_path(self, rel_path: str) -> bool: + """Return whether a working-dir file is a supported external leaf.""" + urlpath = os.path.join(self.working_dir, rel_path) + try: + opened = blosc2.blosc2_ext.open( + urlpath, + mode="r", + offset=0, + mmap_mode=self.mmap_mode, + dparams=self.dparams, + ) + except Exception: + return False + return self._opened_external_kind(opened, rel_path) is not None + + def _probe_external_leaf_offset(self, filepath: str) -> bool: + """Return whether a zip member is a supported external leaf.""" + offset = self.offsets[filepath]["offset"] + try: + opened = blosc2.blosc2_ext.open( + self.b2z_path, + mode="r", + offset=offset, + mmap_mode=self.mmap_mode, + dparams=self.dparams, + ) + except Exception: + return False + return self._opened_external_kind(opened, filepath) is not None + def _init_write_append_mode( self, cparams: blosc2.CParams | None, @@ -220,31 +311,52 @@ def _init_write_append_mode( self._update_map_tree() def _update_map_tree(self): - # Build map_tree from .b2nd and .b2f files in working dir + # Build map_tree from supported external leaves in working dir. for root, _, files in os.walk(self.working_dir): for file in files: filepath = os.path.join(root, file) - if filepath.endswith((".b2nd", ".b2f")): - # Convert filename to key: remove extension and ensure starts with / - rel_path = os.path.relpath(filepath, self.working_dir) - # Normalize path separators to forward slashes for cross-platform consistency - rel_path = rel_path.replace(os.sep, "/") - if rel_path.endswith(".b2nd"): - key = rel_path[:-5] - elif rel_path.endswith(".b2f"): - key = rel_path[:-4] - else: - continue - if not key.startswith("/"): - key = "/" + key - self.map_tree[key] = rel_path + if os.path.abspath(filepath) == os.path.abspath(self.estore_path): + continue + rel_path = os.path.relpath(filepath, self.working_dir).replace(os.sep, "/") + if self._probe_external_leaf_path(rel_path): + self.map_tree[self._logical_key_from_relpath(rel_path)] = rel_path + + def _update_map_tree_from_offsets(self): + """Build map_tree from supported external leaves in a zip store.""" + for filepath in self.offsets: + if filepath == "embed.b2e": + continue + if self._probe_external_leaf_offset(filepath): + self.map_tree[self._logical_key_from_relpath(filepath)] = filepath @property def estore(self) -> EmbedStore: """Access the underlying EmbedStore.""" return self._estore - def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: + @staticmethod + def _value_nbytes(value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore) -> int: + if isinstance(value, blosc2.VLArray | blosc2.BatchStore): + return value.schunk.nbytes + return value.nbytes + + @staticmethod + def _is_external_value(value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore) -> bool: + return isinstance(value, blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore) and bool( + getattr(value, "urlpath", None) + ) + + @staticmethod + def _external_ext(value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore) -> str: + if isinstance(value, blosc2.NDArray): + return ".b2nd" + if isinstance(value, blosc2.BatchStore): + return ".b2b" + return ".b2f" + + def __setitem__( + self, key: str, value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore + ) -> None: """Add a node to the DictStore.""" if isinstance(value, np.ndarray): value = blosc2.asarray(value, cparams=self.cparams, dparams=self.dparams) @@ -252,12 +364,10 @@ def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: if isinstance(value, C2Array): self._estore[key] = value return - exceeds_threshold = self.threshold is not None and value.nbytes >= self.threshold - # Consider both NDArray and SChunk external files (have urlpath) - external_file = isinstance(value, (blosc2.NDArray, SChunk)) and getattr(value, "urlpath", None) + exceeds_threshold = self.threshold is not None and self._value_nbytes(value) >= self.threshold + external_file = self._is_external_value(value) if exceeds_threshold or (external_file and self.threshold is None): - # Choose extension based on type - ext = ".b2f" if isinstance(value, SChunk) else ".b2nd" + ext = self._external_ext(value) # Convert key to a proper file path within the tree directory rel_key = key.lstrip("/") dest_path = os.path.join(self.working_dir, rel_key + ext) @@ -272,7 +382,7 @@ def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: if hasattr(value, "save"): value.save(urlpath=dest_path) else: - # An SChunk does not have a save() method + # SChunk, VLArray and BatchStore can all be persisted via their cframe. with open(dest_path, "wb") as f: f.write(value.to_cframe()) else: @@ -290,20 +400,23 @@ def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: value = blosc2.from_cframe(value.to_cframe()) self._estore[key] = value - def __getitem__(self, key: str) -> blosc2.NDArray | SChunk | C2Array: + def __getitem__( + self, key: str + ) -> blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore | C2Array: """Retrieve a node from the DictStore.""" # Check map_tree first if key in self.map_tree: filepath = self.map_tree[key] if filepath in self.offsets: offset = self.offsets[filepath]["offset"] - return blosc2.blosc2_ext.open( + opened = blosc2.blosc2_ext.open( self.b2z_path, mode="r", offset=offset, mmap_mode=self.mmap_mode, dparams=self.dparams, ) + return _process_opened_object(opened) else: urlpath = os.path.join(self.working_dir, filepath) if os.path.exists(urlpath): @@ -319,7 +432,9 @@ def __getitem__(self, key: str) -> blosc2.NDArray | SChunk | C2Array: # Fall back to EmbedStore return self._estore[key] - def get(self, key: str, default: Any = None) -> blosc2.NDArray | SChunk | C2Array | Any: + def get( + self, key: str, default: Any = None + ) -> blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore | C2Array | Any: """Retrieve a node, or default if not found.""" try: return self[key] @@ -372,12 +487,14 @@ def values(self) -> Iterator[blosc2.NDArray | SChunk | C2Array]: if self.is_zip_store: if filepath in self.offsets: offset = self.offsets[filepath]["offset"] - yield blosc2.blosc2_ext.open( - self.b2z_path, - mode="r", - offset=offset, - mmap_mode=self.mmap_mode, - dparams=self.dparams, + yield _process_opened_object( + blosc2.blosc2_ext.open( + self.b2z_path, + mode="r", + offset=offset, + mmap_mode=self.mmap_mode, + dparams=self.dparams, + ) ) else: urlpath = os.path.join(self.working_dir, filepath) @@ -404,12 +521,14 @@ def items(self) -> Iterator[tuple[str, blosc2.NDArray | SChunk | C2Array]]: offset = self.offsets[filepath]["offset"] yield ( key, - blosc2.blosc2_ext.open( - self.b2z_path, - mode="r", - offset=offset, - mmap_mode=self.mmap_mode, - dparams=self.dparams, + _process_opened_object( + blosc2.blosc2_ext.open( + self.b2z_path, + mode="r", + offset=offset, + mmap_mode=self.mmap_mode, + dparams=self.dparams, + ) ), ) else: @@ -436,14 +555,19 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str: If True, overwrite the existing b2z file if it exists. Default is False. filename : str, optional If provided, use this filename instead of the default b2z file path. + Keyword use is recommended for clarity. Returns ------- filename : str The absolute path to the created b2z file. """ - if self.mode == "r": - raise ValueError("Cannot call to_b2z() on a DictStore opened in read mode.") + if isinstance(overwrite, str | os.PathLike) and filename is None: + filename = overwrite + overwrite = False + + if self.mode == "r" and self.is_zip_store: + raise ValueError("Cannot call to_b2z() on a .b2z DictStore opened in read mode.") b2z_path = self.b2z_path if filename is None else filename if not b2z_path.endswith(".b2z"): @@ -463,7 +587,7 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str: # Sort filepaths by file size from largest to smallest filepaths.sort(key=os.path.getsize, reverse=True) - with zipfile.ZipFile(self.b2z_path, "w", zipfile.ZIP_STORED) as zf: + with zipfile.ZipFile(b2z_path, "w", zipfile.ZIP_STORED) as zf: # Write all files (except estore_path) first (sorted by size) for filepath in filepaths: arcname = os.path.relpath(filepath, self.working_dir) @@ -472,7 +596,7 @@ def to_b2z(self, overwrite=False, filename=None) -> os.PathLike[Any] | str: if os.path.exists(self.estore_path): arcname = os.path.relpath(self.estore_path, self.working_dir) zf.write(self.estore_path, arcname) - return os.path.abspath(self.b2z_path) + return os.path.abspath(b2z_path) def _get_zip_offsets(self) -> dict[str, dict[str, int]]: """Get offset and length of all files in the zip archive.""" diff --git a/src/blosc2/embed_store.py b/src/blosc2/embed_store.py index 84b1b200..2497cad2 100644 --- a/src/blosc2/embed_store.py +++ b/src/blosc2/embed_store.py @@ -5,15 +5,20 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### +from __future__ import annotations + import copy -from collections.abc import Iterator, KeysView -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np import blosc2 from blosc2.c2array import C2Array -from blosc2.schunk import SChunk + +if TYPE_CHECKING: + from collections.abc import Iterator, KeysView + + from blosc2.schunk import SChunk PROFILE = False # Set to True to enable PROFILE prints in EmbedStore @@ -168,7 +173,9 @@ def _ensure_capacity(self, needed_bytes: int) -> None: new_size = max(required_size, int(self._store.shape[0] * 1.5)) self._store.resize((new_size,)) - def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: + def __setitem__( + self, key: str, value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore + ) -> None: """Add a node to the embed store.""" if self.mode == "r": raise ValueError("Cannot set items in read-only mode.") @@ -191,7 +198,7 @@ def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: self._embed_map[key] = {"offset": offset, "length": data_len} self._save_metadata() - def __getitem__(self, key: str) -> blosc2.NDArray | SChunk: + def __getitem__(self, key: str) -> blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore: """Retrieve a node from the embed store.""" if key not in self._embed_map: raise KeyError(f"Key '{key}' not found in the embed store.") @@ -207,7 +214,9 @@ def __getitem__(self, key: str) -> blosc2.NDArray | SChunk: # Use from_cframe so we can deserialize either an NDArray or an SChunk return blosc2.from_cframe(serialized_data, copy=True) - def get(self, key: str, default: Any = None) -> blosc2.NDArray | SChunk | Any: + def get( + self, key: str, default: Any = None + ) -> blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore | Any: """Retrieve a node, or default if not found.""" return self[key] if key in self._embed_map else default @@ -234,12 +243,12 @@ def keys(self) -> KeysView[str]: """Return all keys.""" return self._embed_map.keys() - def values(self) -> Iterator[blosc2.NDArray | SChunk]: + def values(self) -> Iterator[blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore]: """Iterate over all values.""" for key in self._embed_map: yield self[key] - def items(self) -> Iterator[tuple[str, blosc2.NDArray | SChunk]]: + def items(self) -> Iterator[tuple[str, blosc2.NDArray | SChunk | blosc2.VLArray | blosc2.BatchStore]]: """Iterate over (key, value) pairs.""" for key in self._embed_map: yield key, self[key] diff --git a/src/blosc2/info.py b/src/blosc2/info.py index 4ac629da..ef1e3011 100644 --- a/src/blosc2/info.py +++ b/src/blosc2/info.py @@ -10,6 +10,22 @@ from textwrap import TextWrapper +def format_nbytes_human(nbytes: int) -> str: + units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB") + value = float(nbytes) + for unit in units: + if value < 1024.0 or unit == units[-1]: + if unit == "B": + return f"{nbytes} B" + return f"{value:.2f} {unit}" + value /= 1024.0 + return None + + +def format_nbytes_info(nbytes: int) -> str: + return f"{nbytes} ({format_nbytes_human(nbytes)})" + + def info_text_report_(items: list) -> str: with io.StringIO() as buf: print(items, file=buf) diff --git a/src/blosc2/lazyexpr.py b/src/blosc2/lazyexpr.py index 9b88930c..c6893686 100644 --- a/src/blosc2/lazyexpr.py +++ b/src/blosc2/lazyexpr.py @@ -68,6 +68,7 @@ process_key, reducers, safe_numpy_globals, + try_miniexpr, ) if not blosc2.IS_WASM: @@ -76,14 +77,6 @@ global safe_blosc2_globals safe_blosc2_globals = {} -# Set this to False if miniexpr should not be tried out -try_miniexpr = not blosc2.IS_WASM or getattr(blosc2, "_WASM_MINIEXPR_ENABLED", False) - - -def _toggle_miniexpr(FLAG): - global try_miniexpr - try_miniexpr = FLAG - def ne_evaluate(expression, local_dict=None, **kwargs): """Safely evaluate expressions using numexpr when possible, falling back to numpy.""" diff --git a/src/blosc2/linalg.py b/src/blosc2/linalg.py index b1bda5e7..894fc216 100644 --- a/src/blosc2/linalg.py +++ b/src/blosc2/linalg.py @@ -17,12 +17,84 @@ import blosc2 -from .utils import get_intersecting_chunks, nptranspose, npvecdot, slice_to_chunktuple +from .utils import get_intersecting_chunks, nptranspose, npvecdot, slice_to_chunktuple, try_miniexpr if TYPE_CHECKING: from collections.abc import Sequence +def _matmul_chunked( + x1: blosc2.Array, x2: blosc2.NDArray, result: blosc2.NDArray, n: int, m: int, k: int +) -> None: + p, q = result.chunks[-2:] + r = x2.chunks[-1] + + intersecting_chunks = get_intersecting_chunks((), result.shape[:-2], result.chunks[:-2]) + for chunk in intersecting_chunks: + chunk = chunk.raw + for row in range(0, n, p): + row_end = builtins.min(row + p, n) + for col in range(0, m, q): + col_end = builtins.min(col + q, m) + for aux in range(0, k, r): + aux_end = builtins.min(aux + r, k) + bx1 = ( + x1[chunk[-x1.ndim + 2 :] + (slice(row, row_end), slice(aux, aux_end))] + if x1.ndim > 2 + else x1[row:row_end, aux:aux_end] + ) + bx2 = ( + x2[chunk[-x2.ndim + 2 :] + (slice(aux, aux_end), slice(col, col_end))] + if x2.ndim > 2 + else x2[aux:aux_end, col:col_end] + ) + result[chunk + (slice(row, row_end), slice(col, col_end))] += np.matmul(bx1, bx2) + + +def _matmul_can_use_fast_path( + x1: blosc2.Array, x2: blosc2.NDArray, result: blosc2.NDArray, use_miniexpr: bool +) -> bool: + if not use_miniexpr: + return False + + ops = (x1, x2, result) + all_ndarray = all(isinstance(value, blosc2.NDArray) and value.shape != () for value in ops) + if not all_ndarray: + return False + + # The current prefilter-backed implementation is only supported for 2-D layouts. + if result.ndim != 2 or x1.ndim != 2 or x2.ndim != 2: + return False + + if any(op.dtype != ops[0].dtype for op in ops): + return False + + chunks_aligned = x1.chunks[-2] % x1.blocks[-2] == 0 + chunks_aligned &= x2.chunks[-1] % x2.blocks[-1] == 0 + chunks_aligned &= x2.chunks[-2] % x1.blocks[-1] == 0 + if not chunks_aligned: + return False + + same_blocks = x2.blocks[-2] == x1.blocks[-1] + same_blocks &= x2.blocks[-1] == result.blocks[-1] + same_blocks &= result.blocks[-2] == x1.blocks[-2] + if not same_blocks: + return False + + try: + result_blocks = np.broadcast_shapes(x1.blocks, x2.blocks) + except ValueError: + return False + if result_blocks[:-2] != result.blocks[:-2]: + return False + + if x1.dtype.kind != "f": + return False + if x2.dtype.kind != "f": + return False + return x1.dtype == x2.dtype + + def matmul(x1: blosc2.Array, x2: blosc2.NDArray, **kwargs: Any) -> blosc2.NDArray: """ Computes the matrix product between two Blosc2 NDArrays. @@ -112,30 +184,28 @@ def matmul(x1: blosc2.Array, x2: blosc2.NDArray, **kwargs: Any) -> blosc2.NDArra kwargs["_chunksize_reduc_factor"] = 1 result = blosc2.zeros(result_shape, dtype=blosc2.result_type(x1, x2), **kwargs) + global try_miniexpr + if 0 not in result.shape + x1.shape + x2.shape: # if any array is empty, return array of 0s - p, q = result.chunks[-2:] - r = x2.chunks[-1] - - intersecting_chunks = get_intersecting_chunks((), result.shape[:-2], result.chunks[:-2]) - for chunk in intersecting_chunks: - chunk = chunk.raw - for row in range(0, n, p): - row_end = builtins.min(row + p, n) - for col in range(0, m, q): - col_end = builtins.min(col + q, m) - for aux in range(0, k, r): - aux_end = builtins.min(aux + r, k) - bx1 = ( - x1[chunk[-x1.ndim + 2 :] + (slice(row, row_end), slice(aux, aux_end))] - if x1.ndim > 2 - else x1[row:row_end, aux:aux_end] - ) - bx2 = ( - x2[chunk[-x2.ndim + 2 :] + (slice(aux, aux_end), slice(col, col_end))] - if x2.ndim > 2 - else x2[aux:aux_end, col:col_end] - ) - result[chunk + (slice(row, row_end), slice(col, col_end))] += np.matmul(bx1, bx2) + if _matmul_can_use_fast_path(x1, x2, result, try_miniexpr): + prefilter_set = False + try: + result._set_pref_matmul({"x1": x1, "x2": x2}, fp_accuracy=blosc2.FPAccuracy.DEFAULT) + prefilter_set = True + # Data to compress is fetched from operands, so it can be uninitialized here + data = np.empty(result.schunk.chunksize, dtype=np.uint8) + for nchunk_out in range(result.schunk.nchunks): + result.schunk.update_data(nchunk_out, data, copy=False) + except Exception as exc: + warnings.warn( + f"Fast matmul path unavailable; falling back to chunked path: {exc}", RuntimeWarning + ) + _matmul_chunked(x1, x2, result, n, m, k) + finally: + if prefilter_set: + result.schunk.remove_prefilter("miniexpr") + else: + _matmul_chunked(x1, x2, result, n, m, k) if x1_is_vector: result = result.squeeze(axis=-2) diff --git a/src/blosc2/ndarray.py b/src/blosc2/ndarray.py index c8681f58..4c35cef6 100644 --- a/src/blosc2/ndarray.py +++ b/src/blosc2/ndarray.py @@ -29,7 +29,7 @@ import blosc2 from blosc2 import SpecialValue, blosc2_ext, compute_chunks_blocks -from blosc2.info import InfoReporter +from blosc2.info import InfoReporter, format_nbytes_info from blosc2.schunk import SChunk from .linalg import matmul @@ -3838,8 +3838,8 @@ def info_items(self) -> list: items += [("chunks", self.chunks)] items += [("blocks", self.blocks)] items += [("dtype", self.dtype)] - items += [("nbytes", self.nbytes)] - items += [("cbytes", self.cbytes)] + items += [("nbytes", format_nbytes_info(self.nbytes))] + items += [("cbytes", format_nbytes_info(self.cbytes))] items += [("cratio", f"{self.cratio:.2f}")] items += [("cparams", self.cparams)] items += [("dparams", self.dparams)] @@ -4703,7 +4703,7 @@ def save(self, urlpath: str, contiguous=True, **kwargs: Any) -> None: # Add the contiguous parameter kwargs["contiguous"] = contiguous - super().copy(self.dtype, **kwargs) + super().copy(self.dtype, cparams=asdict(self.cparams), **kwargs) def resize(self, newshape: tuple | list) -> None: """Change the shape of the array by growing or shrinking one or more dimensions. diff --git a/src/blosc2/schunk.py b/src/blosc2/schunk.py index cd2dbe9d..55b4acdf 100644 --- a/src/blosc2/schunk.py +++ b/src/blosc2/schunk.py @@ -16,11 +16,11 @@ from typing import Any, NamedTuple import numpy as np -from msgpack import packb, unpackb import blosc2 from blosc2 import SpecialValue, blosc2_ext -from blosc2.info import InfoReporter +from blosc2._msgpack_utils import msgpack_packb, msgpack_unpackb +from blosc2.info import InfoReporter, format_nbytes_info class vlmeta(MutableMapping, blosc2_ext.vlmeta): @@ -46,12 +46,7 @@ def __setitem__(self, name, content): return raise NotImplementedError("Slicing is not supported, unless [:]") cparams = {"typesize": 1} - content = packb( - content, - default=blosc2_ext.encode_tuple, - strict_types=True, - use_bin_type=True, - ) + content = msgpack_packb(content) super().set_vlmeta(name, content, **cparams) def __getitem__(self, name): @@ -60,7 +55,7 @@ def __getitem__(self, name): # Return all the vlmetalayers return self.getall() raise NotImplementedError("Slicing is not supported, unless [:]") - return unpackb(super().get_vlmeta(name), list_hook=blosc2_ext.decode_tuple) + return msgpack_unpackb(super().get_vlmeta(name)) def __delitem__(self, name): blosc2_ext.check_access_mode(self.urlpath, self.mode) @@ -120,7 +115,7 @@ def __setitem__(self, key: str, value: bytes) -> None: ..warning: Note that the *length* of the metalayer cannot change, otherwise an exception will be raised. """ - value = packb(value, default=blosc2_ext.encode_tuple, strict_types=True, use_bin_type=True) + value = msgpack_packb(value) blosc2_ext.meta__setitem__(self.schunk, key, value) def __getitem__(self, item: str | slice) -> bytes | dict[str, bytes]: @@ -144,10 +139,7 @@ def __getitem__(self, item: str | slice) -> bytes | dict[str, bytes]: return self.getall() raise NotImplementedError("Slicing is not supported, unless [:]") if self.__contains__(item): - return unpackb( - blosc2_ext.meta__getitem__(self.schunk, item), - list_hook=blosc2_ext.decode_tuple, - ) + return msgpack_unpackb(blosc2_ext.meta__getitem__(self.schunk, item)) else: raise KeyError(f"{item} not found") @@ -499,8 +491,8 @@ def info_items(self) -> list: items += [("chunksize", self.chunksize)] items += [("blocksize", self.blocksize)] items += [("typesize", self.typesize)] - items += [("nbytes", self.nbytes)] - items += [("cbytes", self.cbytes)] + items += [("nbytes", format_nbytes_info(self.nbytes))] + items += [("cbytes", format_nbytes_info(self.cbytes))] items += [("cratio", f"{self.cratio:.2f}")] items += [("cparams", self.cparams)] items += [("dparams", self.dparams)] @@ -682,6 +674,10 @@ def get_chunk(self, nchunk: int) -> bytes: """ return super().get_chunk(nchunk) + def get_vlblock(self, nchunk: int, nblock: int) -> bytes: + """Return the decompressed payload of one VL block from a chunk.""" + return super().get_vlblock(nchunk, nblock) + def delete_chunk(self, nchunk: int) -> int: """Delete the specified chunk from the SChunk. @@ -801,6 +797,27 @@ def insert_data(self, nchunk: int, data: object, copy: bool) -> int: blosc2_ext.check_access_mode(self.urlpath, self.mode) return super().insert_data(nchunk, data, copy) + def append_chunk(self, chunk: bytes) -> int: + """Append a compressed chunk to the end of the SChunk. + + Parameters + ---------- + chunk: bytes object + The compressed chunk to append. + + Returns + ------- + out: int + The number of chunks in the SChunk. + + Raises + ------ + RuntimeError + If the chunk could not be appended. + """ + blosc2_ext.check_access_mode(self.urlpath, self.mode) + return super().append_chunk(chunk) + def update_chunk(self, nchunk: int, chunk: bytes) -> int: """Update an existing chunk in the SChunk. @@ -1603,6 +1620,16 @@ def _process_opened_object(res): elif not proxy_src["caterva2_env"]: raise RuntimeError("Could not find the source when opening a Proxy") + if "vlarray" in meta: + from blosc2.vlarray import VLArray + + return VLArray(_from_schunk=getattr(res, "schunk", res)) + + if "batchstore" in meta: + from blosc2.batch_store import BatchStore + + return BatchStore(_from_schunk=getattr(res, "schunk", res)) + if isinstance(res, blosc2.NDArray) and "LazyArray" in res.schunk.meta: return blosc2._open_lazyarray(res) else: @@ -1614,6 +1641,8 @@ def open( ) -> ( blosc2.SChunk | blosc2.NDArray + | blosc2.BatchStore + | blosc2.VLArray | blosc2.C2Array | blosc2.LazyArray | blosc2.Proxy diff --git a/src/blosc2/storage.py b/src/blosc2/storage.py index 43835118..0015aea9 100644 --- a/src/blosc2/storage.py +++ b/src/blosc2/storage.py @@ -46,7 +46,9 @@ class CParams: (maximum compression). Default is 1. use_dict: bool Whether to use dictionaries when compressing - (only for :py:obj:`blosc2.Codec.ZSTD `). Default is `False`. + (supported for :py:obj:`blosc2.Codec.ZSTD `, + :py:obj:`blosc2.Codec.LZ4 `, and + :py:obj:`blosc2.Codec.LZ4HC `). Default is `False`. typesize: int The data type size, ranging from 1 to 255. Default is 8. nthreads: int diff --git a/src/blosc2/tree_store.py b/src/blosc2/tree_store.py index 6aad8165..9be6672f 100644 --- a/src/blosc2/tree_store.py +++ b/src/blosc2/tree_store.py @@ -5,6 +5,8 @@ # SPDX-License-Identifier: BSD-3-Clause ####################################################################### +from __future__ import annotations + import contextlib import os from collections.abc import Iterator, MutableMapping @@ -14,11 +16,11 @@ import blosc2 from blosc2.dict_store import DictStore -from blosc2.schunk import SChunk if TYPE_CHECKING: from blosc2.c2array import C2Array from blosc2.ndarray import NDArray + from blosc2.schunk import SChunk class vlmetaProxy(MutableMapping): @@ -29,7 +31,7 @@ class vlmetaProxy(MutableMapping): - Delegates iteration and length to the underlying vlmeta object. """ - def __init__(self, tstore: "TreeStore", inner_vlmeta): + def __init__(self, tstore: TreeStore, inner_vlmeta): self._tstore = tstore self._inner = inner_vlmeta @@ -224,7 +226,9 @@ def _validate_key(self, key: str) -> str: return key - def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: + def __setitem__( + self, key: str, value: blosc2.Array | SChunk | blosc2.VLArray | blosc2.BatchStore + ) -> None: """Add a node with hierarchical key validation. Parameters @@ -266,7 +270,9 @@ def __setitem__(self, key: str, value: blosc2.Array | SChunk) -> None: full_key = self._translate_key_to_full(key) super().__setitem__(full_key, value) - def __getitem__(self, key: str) -> "NDArray | C2Array | SChunk | TreeStore": + def __getitem__( + self, key: str + ) -> NDArray | C2Array | SChunk | blosc2.VLArray | blosc2.BatchStore | TreeStore: """Retrieve a node or subtree view. If the key points to a subtree (intermediate path with children), @@ -280,7 +286,7 @@ def __getitem__(self, key: str) -> "NDArray | C2Array | SChunk | TreeStore": Returns ------- - out : blosc2.NDArray or blosc2.C2Array or blosc2.SChunk or TreeStore + out : blosc2.NDArray or blosc2.C2Array or blosc2.SChunk or blosc2.VLArray or blosc2.BatchStore or TreeStore The stored array/chunk if key is a leaf node, or a TreeStore subtree view if key is an intermediate path with children. @@ -416,7 +422,7 @@ def __iter__(self) -> Iterator[str]: """Iterate over keys, excluding vlmeta keys.""" return iter(self.keys()) - def items(self) -> Iterator[tuple[str, "NDArray | C2Array | SChunk | TreeStore"]]: + def items(self) -> Iterator[tuple[str, NDArray | C2Array | SChunk | TreeStore]]: """Return key-value pairs in the current subtree view.""" for key in self.keys(): yield key, self[key] @@ -575,7 +581,7 @@ def walk(self, path: str = "/", topdown: bool = True) -> Iterator[tuple[str, lis # Yield current level after children (post-order) yield path, children_dirs, leaf_nodes - def get_subtree(self, path: str) -> "TreeStore": + def get_subtree(self, path: str) -> TreeStore: """Create a subtree view with the specified path as root. Parameters @@ -662,8 +668,15 @@ def _persist_vlmeta(self) -> None: """ if hasattr(self, "_vlmeta_key"): vlmeta_key = self._vlmeta_key - # Only embedded case is expected; handle it safely. - if hasattr(self, "_estore") and vlmeta_key in self._estore: + if vlmeta_key in self.map_tree: + filepath = self.map_tree[vlmeta_key] + dest_path = os.path.join(self.working_dir, filepath) + parent_dir = os.path.dirname(dest_path) + if parent_dir and not os.path.exists(parent_dir): + os.makedirs(parent_dir, exist_ok=True) + with open(dest_path, "wb") as f: + f.write(self._vlmeta.to_cframe()) + elif hasattr(self, "_estore") and vlmeta_key in self._estore: # Replace the stored snapshot with contextlib.suppress(KeyError): del self._estore[vlmeta_key] diff --git a/src/blosc2/utils.py b/src/blosc2/utils.py index 6a72f2ef..67434a3a 100644 --- a/src/blosc2/utils.py +++ b/src/blosc2/utils.py @@ -9,6 +9,7 @@ import builtins import inspect import math +import sys import warnings from itertools import product @@ -19,6 +20,19 @@ import blosc2 +# Set this to False if miniexpr should not be tried out +try_miniexpr = not blosc2.IS_WASM or getattr(blosc2, "_WASM_MINIEXPR_ENABLED", False) + + +def _toggle_miniexpr(FLAG): + global try_miniexpr + try_miniexpr = FLAG + for module_name in ("blosc2.lazyexpr", "blosc2.linalg"): + module = sys.modules.get(module_name) + if module is not None: + module.try_miniexpr = FLAG + + # NumPy version and a convenient boolean flag NUMPY_GE_2_0 = np.__version__ >= "2.0" # handle different numpy versions diff --git a/src/blosc2/vlarray.py b/src/blosc2/vlarray.py new file mode 100644 index 00000000..18c737a6 --- /dev/null +++ b/src/blosc2/vlarray.py @@ -0,0 +1,410 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +from __future__ import annotations + +import copy +import pathlib +from typing import TYPE_CHECKING, Any + +import blosc2 +from blosc2._msgpack_utils import msgpack_packb, msgpack_unpackb +from blosc2.info import InfoReporter, format_nbytes_info + +if TYPE_CHECKING: + from collections.abc import Iterator + + from blosc2.schunk import SChunk + +_VLARRAY_META = {"version": 1, "serializer": "msgpack"} + + +def _check_serialized_size(buffer: bytes) -> None: + if len(buffer) > blosc2.MAX_BUFFERSIZE: + raise ValueError(f"Serialized objects cannot be larger than {blosc2.MAX_BUFFERSIZE} bytes") + + +class VLArray: + """A variable-length array backed by an :class:`blosc2.SChunk`.""" + + @staticmethod + def _set_typesize_one(cparams: blosc2.CParams | dict | None) -> blosc2.CParams | dict: + auto_use_dict = cparams is None + if cparams is None: + cparams = blosc2.CParams() + elif isinstance(cparams, blosc2.CParams): + cparams = copy.deepcopy(cparams) + else: + cparams = dict(cparams) + auto_use_dict = "use_dict" not in cparams + + if isinstance(cparams, blosc2.CParams): + cparams.typesize = 1 + if auto_use_dict and cparams.codec == blosc2.Codec.ZSTD and cparams.clevel > 0: + # VLArray stores many small serialized payloads, where Zstd dicts help materially. + cparams.use_dict = True + else: + cparams["typesize"] = 1 + codec = cparams.get("codec", blosc2.Codec.ZSTD) + clevel = cparams.get("clevel", 5) + if auto_use_dict and codec == blosc2.Codec.ZSTD and clevel > 0: + # VLArray stores many small serialized payloads, where Zstd dicts help materially. + cparams["use_dict"] = True + return cparams + + @staticmethod + def _coerce_storage(storage: blosc2.Storage | dict | None, kwargs: dict[str, Any]) -> blosc2.Storage: + if storage is not None: + storage_keys = set(blosc2.Storage.__annotations__) + storage_kwargs = storage_keys.intersection(kwargs) + if storage_kwargs: + unexpected = ", ".join(sorted(storage_kwargs)) + raise AttributeError( + f"Cannot pass both `storage` and other kwargs already included in Storage: {unexpected}" + ) + if isinstance(storage, blosc2.Storage): + return copy.deepcopy(storage) + return blosc2.Storage(**storage) + + storage_kwargs = { + name: kwargs.pop(name) for name in list(blosc2.Storage.__annotations__) if name in kwargs + } + return blosc2.Storage(**storage_kwargs) + + @staticmethod + def _validate_storage(storage: blosc2.Storage) -> None: + if storage.mmap_mode not in (None, "r"): + raise ValueError("For VLArray containers, mmap_mode must be None or 'r'") + if storage.mmap_mode == "r" and storage.mode != "r": + raise ValueError("For VLArray containers, mmap_mode='r' requires mode='r'") + + def _attach_schunk(self, schunk: SChunk) -> None: + self.schunk = schunk + self.mode = schunk.mode + self.mmap_mode = getattr(schunk, "mmap_mode", None) + self._validate_tag() + + def _maybe_open_existing(self, storage: blosc2.Storage) -> bool: + urlpath = storage.urlpath + if urlpath is None or storage.mode not in ("r", "a") or not pathlib.Path(urlpath).exists(): + return False + + schunk = blosc2.blosc2_ext.open(urlpath, mode=storage.mode, offset=0, mmap_mode=storage.mmap_mode) + self._attach_schunk(schunk) + return True + + def _make_storage(self) -> blosc2.Storage: + meta = {name: self.meta[name] for name in self.meta} + return blosc2.Storage( + contiguous=self.schunk.contiguous, + urlpath=self.urlpath, + mode=self.mode, + mmap_mode=self.mmap_mode, + meta=meta, + ) + + def __init__( + self, + chunksize: int | None = None, + _from_schunk: SChunk | None = None, + **kwargs: Any, + ) -> None: + if _from_schunk is not None: + if chunksize is not None: + raise ValueError("Cannot pass `chunksize` together with `_from_schunk`") + if kwargs: + unexpected = ", ".join(sorted(kwargs)) + raise ValueError(f"Cannot pass {unexpected} together with `_from_schunk`") + self._attach_schunk(_from_schunk) + return + + cparams = kwargs.pop("cparams", None) + dparams = kwargs.pop("dparams", None) + storage = kwargs.pop("storage", None) + storage = self._coerce_storage(storage, kwargs) + + if kwargs: + unexpected = ", ".join(sorted(kwargs)) + raise ValueError(f"Unsupported VLArray keyword argument(s): {unexpected}") + + self._validate_storage(storage) + cparams = self._set_typesize_one(cparams) + + if dparams is None: + dparams = blosc2.DParams() + + if self._maybe_open_existing(storage): + return + + fixed_meta = dict(storage.meta or {}) + fixed_meta["vlarray"] = dict(_VLARRAY_META) + storage.meta = fixed_meta + if chunksize is None: + chunksize = -1 + schunk = blosc2.SChunk( + chunksize=chunksize, data=None, cparams=cparams, dparams=dparams, storage=storage + ) + self._attach_schunk(schunk) + + def _validate_tag(self) -> None: + if "vlarray" not in self.schunk.meta: + raise ValueError("The supplied SChunk is not tagged as a VLArray") + + def _check_writable(self) -> None: + if self.mode == "r": + raise ValueError("Cannot modify a VLArray opened in read-only mode") + + def _normalize_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("VLArray indices must be integers") + if index < 0: + index += len(self) + if index < 0 or index >= len(self): + raise IndexError("VLArray index out of range") + return index + + def _normalize_insert_index(self, index: int) -> int: + if not isinstance(index, int): + raise TypeError("VLArray indices must be integers") + if index < 0: + index += len(self) + if index < 0: + return 0 + if index > len(self): + return len(self) + return index + + def _slice_indices(self, index: slice) -> list[int]: + return list(range(*index.indices(len(self)))) + + def _copy_meta(self) -> dict[str, Any]: + return {name: self.meta[name] for name in self.meta} + + def _item_size_stats(self) -> tuple[list[int], list[int]]: + item_nbytes = [] + chunk_cbytes = [] + for i in range(len(self)): + nbytes, cbytes, _ = blosc2.get_cbuffer_sizes(self.schunk.get_lazychunk(i)) + item_nbytes.append(nbytes) + chunk_cbytes.append(cbytes) + return item_nbytes, chunk_cbytes + + def _serialize(self, value: Any) -> bytes: + payload = msgpack_packb(value) + _check_serialized_size(payload) + return payload + + def _compress(self, payload: bytes) -> bytes: + return blosc2.compress2(payload, cparams=self.schunk.cparams) + + def append(self, value: Any) -> int: + """Append one value and return the new number of entries.""" + self._check_writable() + chunk = self._compress(self._serialize(value)) + return self.schunk.append_chunk(chunk) + + def insert(self, index: int, value: Any) -> int: + """Insert one value at ``index`` and return the new number of entries.""" + self._check_writable() + index = self._normalize_insert_index(index) + chunk = self._compress(self._serialize(value)) + return self.schunk.insert_chunk(index, chunk) + + def delete(self, index: int) -> int: + """Delete the value at ``index`` and return the new number of entries.""" + self._check_writable() + if isinstance(index, slice): + for idx in reversed(self._slice_indices(index)): + self.schunk.delete_chunk(idx) + return len(self) + index = self._normalize_index(index) + return self.schunk.delete_chunk(index) + + def pop(self, index: int = -1) -> Any: + """Remove and return the value at ``index``.""" + self._check_writable() + if isinstance(index, slice): + raise NotImplementedError("Slicing is not supported for VLArray") + index = self._normalize_index(index) + value = self[index] + self.schunk.delete_chunk(index) + return value + + def extend(self, values: object) -> None: + """Append all values from an iterable.""" + self._check_writable() + for value in values: + chunk = self._compress(self._serialize(value)) + self.schunk.append_chunk(chunk) + + def clear(self) -> None: + """Remove all entries from the container.""" + self._check_writable() + storage = self._make_storage() + if storage.urlpath is not None: + blosc2.remove_urlpath(storage.urlpath) + schunk = blosc2.SChunk( + chunksize=-1, + data=None, + cparams=copy.deepcopy(self.cparams), + dparams=copy.deepcopy(self.dparams), + storage=storage, + ) + self._attach_schunk(schunk) + + def __getitem__(self, index: int) -> Any: + if isinstance(index, slice): + return [self[i] for i in self._slice_indices(index)] + index = self._normalize_index(index) + payload = self.schunk.decompress_chunk(index) + return msgpack_unpackb(payload) + + def __setitem__(self, index: int, value: Any) -> None: + if isinstance(index, slice): + self._check_writable() + indices = self._slice_indices(index) + values = list(value) + step = 1 if index.step is None else index.step + if step == 1: + start = self._normalize_insert_index(0 if index.start is None else index.start) + for idx in reversed(indices): + self.schunk.delete_chunk(idx) + for offset, item in enumerate(values): + chunk = self._compress(self._serialize(item)) + self.schunk.insert_chunk(start + offset, chunk) + return + if len(values) != len(indices): + raise ValueError( + f"attempt to assign sequence of size {len(values)} to extended slice of size {len(indices)}" + ) + for idx, item in zip(indices, values, strict=True): + chunk = self._compress(self._serialize(item)) + self.schunk.update_chunk(idx, chunk) + return + self._check_writable() + index = self._normalize_index(index) + chunk = self._compress(self._serialize(value)) + self.schunk.update_chunk(index, chunk) + + def __delitem__(self, index: int) -> None: + self.delete(index) + + def __len__(self) -> int: + return self.schunk.nchunks + + def __iter__(self) -> Iterator[Any]: + for i in range(len(self)): + yield self[i] + + @property + def meta(self): + return self.schunk.meta + + @property + def vlmeta(self): + return self.schunk.vlmeta + + @property + def cparams(self): + return self.schunk.cparams + + @property + def dparams(self): + return self.schunk.dparams + + @property + def chunksize(self) -> int: + return self.schunk.chunksize + + @property + def typesize(self) -> int: + return self.schunk.typesize + + @property + def nbytes(self) -> int: + return self.schunk.nbytes + + @property + def cbytes(self) -> int: + return self.schunk.cbytes + + @property + def cratio(self) -> float: + return self.schunk.cratio + + @property + def urlpath(self) -> str | None: + return self.schunk.urlpath + + @property + def contiguous(self) -> bool: + return self.schunk.contiguous + + @property + def info(self) -> InfoReporter: + """Print information about this VLArray.""" + return InfoReporter(self) + + @property + def info_items(self) -> list: + """A list of tuples with summary information about this VLArray.""" + item_nbytes, chunk_cbytes = self._item_size_stats() + avg_item_nbytes = sum(item_nbytes) / len(item_nbytes) if item_nbytes else 0.0 + avg_chunk_cbytes = sum(chunk_cbytes) / len(chunk_cbytes) if chunk_cbytes else 0.0 + return [ + ("type", f"{self.__class__.__name__}"), + ("entries", len(self)), + ("item_nbytes_min", min(item_nbytes) if item_nbytes else 0), + ("item_nbytes_max", max(item_nbytes) if item_nbytes else 0), + ("item_nbytes_avg", f"{avg_item_nbytes:.2f}"), + ("chunk_cbytes_min", min(chunk_cbytes) if chunk_cbytes else 0), + ("chunk_cbytes_max", max(chunk_cbytes) if chunk_cbytes else 0), + ("chunk_cbytes_avg", f"{avg_chunk_cbytes:.2f}"), + ("nbytes", format_nbytes_info(self.nbytes)), + ("cbytes", format_nbytes_info(self.cbytes)), + ("cratio", f"{self.cratio:.2f}"), + ("cparams", self.cparams), + ("dparams", self.dparams), + ] + + def to_cframe(self) -> bytes: + return self.schunk.to_cframe() + + def copy(self, **kwargs: Any) -> VLArray: + """Create a copy of the container with optional constructor overrides.""" + if "meta" in kwargs: + raise ValueError("meta should not be passed to copy") + + kwargs["cparams"] = kwargs.get("cparams", copy.deepcopy(self.cparams)) + kwargs["dparams"] = kwargs.get("dparams", copy.deepcopy(self.dparams)) + kwargs["chunksize"] = kwargs.get("chunksize", -1) + + if "storage" not in kwargs: + kwargs["meta"] = self._copy_meta() + kwargs["contiguous"] = kwargs.get("contiguous", self.schunk.contiguous) + if "urlpath" in kwargs and "mode" not in kwargs: + kwargs["mode"] = "w" + + out = VLArray(**kwargs) + out.extend(self) + return out + + def __enter__(self) -> VLArray: + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + return False + + def __repr__(self) -> str: + return f"VLArray(len={len(self)}, urlpath={self.urlpath!r})" + + +def vlarray_from_cframe(cframe: bytes, copy: bool = True) -> VLArray: + """Deserialize a CFrame buffer into a :class:`VLArray`.""" + + schunk = blosc2.schunk_from_cframe(cframe, copy=copy) + return VLArray(_from_schunk=schunk) diff --git a/tests/ndarray/test_linalg.py b/tests/ndarray/test_linalg.py index aa2ddb19..8967e5d2 100644 --- a/tests/ndarray/test_linalg.py +++ b/tests/ndarray/test_linalg.py @@ -6,14 +6,17 @@ ####################################################################### import inspect +import warnings from itertools import permutations import numpy as np import pytest import blosc2 +import blosc2.linalg as blosc2_linalg +import blosc2.utils as utils_mod from blosc2.lazyexpr import linalg_funcs -from blosc2.utils import npvecdot +from blosc2.utils import _toggle_miniexpr, npvecdot # Conditionally import torch for proxy tests try: @@ -69,6 +72,192 @@ def test_matmul(ashape, achunks, ablocks, bshape, bchunks, bblocks, dtype): np.testing.assert_allclose(b2_res[()], np_res, rtol=1e-6) +def test_toggle_miniexpr_updates_linalg_runtime_flag(): + old_flag = utils_mod.try_miniexpr + try: + _toggle_miniexpr(False) + assert utils_mod.try_miniexpr is False + assert blosc2_linalg.try_miniexpr is False + + _toggle_miniexpr(True) + assert utils_mod.try_miniexpr is True + assert blosc2_linalg.try_miniexpr is True + finally: + _toggle_miniexpr(old_flag) + + +def _set_pref_matmul_call_recorder(monkeypatch): + calls = [] + original = blosc2.NDArray._set_pref_matmul + + def wrapped_set_pref_matmul(self, inputs, fp_accuracy): + calls.append((self.shape, inputs["x1"].shape, inputs["x2"].shape, self.dtype)) + return original(self, inputs, fp_accuracy) + + monkeypatch.setattr(blosc2.NDArray, "_set_pref_matmul", wrapped_set_pref_matmul) + return calls + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_matmul_uses_fast_path_for_supported_2d(monkeypatch, dtype): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(400, 400), dtype=dtype, chunks=(200, 200), blocks=(100, 100)) + b = blosc2.full(shape=(400, 400), fill_value=2, dtype=dtype, chunks=(200, 200), blocks=(100, 100)) + + with warnings.catch_warnings(): + # NumPy + Accelerate can emit spurious matmul RuntimeWarnings on macOS arm64. + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(200, 200), blocks=(100, 100)) + expected = np.matmul(a[:], b[:]) + + assert calls == [((400, 400), (400, 400), (400, 400), np.dtype(dtype))] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_matmul_uses_fast_path_with_multiple_inner_blocks(monkeypatch, dtype): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(256, 384), dtype=dtype, chunks=(128, 192), blocks=(64, 64)) + b = blosc2.full(shape=(384, 256), fill_value=2, dtype=dtype, chunks=(192, 128), blocks=(64, 64)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(128, 128), blocks=(64, 64)) + expected = np.matmul(a[:], b[:]) + + assert calls == [((256, 256), (256, 384), (384, 256), np.dtype(dtype))] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +def test_matmul_falls_back_for_integer_inputs(monkeypatch): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(200, 200), dtype=np.int64, chunks=(100, 100), blocks=(50, 50)) + b = blosc2.full(shape=(200, 200), fill_value=2, dtype=np.int64, chunks=(100, 100), blocks=(50, 50)) + + c = blosc2.matmul(a, b, chunks=(100, 100), blocks=(50, 50)) + + assert calls == [] + np.testing.assert_allclose(c[:], np.matmul(a[:], b[:]), rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +def test_matmul_falls_back_for_nd_inputs(monkeypatch): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(2, 40, 40), dtype=np.float64, chunks=(1, 20, 20), blocks=(1, 10, 10)) + b = blosc2.full( + shape=(2, 40, 40), fill_value=2, dtype=np.float64, chunks=(1, 20, 20), blocks=(1, 10, 10) + ) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(1, 20, 20), blocks=(1, 10, 10)) + expected = np.matmul(a[:], b[:]) + + assert calls == [] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +@pytest.mark.parametrize("dtype", [np.float32, np.float64]) +def test_matmul_falls_back_for_misaligned_blocks(monkeypatch, dtype): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(400, 400), dtype=dtype, chunks=(200, 200), blocks=(120, 100)) + b = blosc2.full(shape=(400, 400), fill_value=2, dtype=dtype, chunks=(200, 200), blocks=(100, 100)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(200, 200), blocks=(120, 100)) + expected = np.matmul(a[:], b[:]) + + assert calls == [] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +def test_matmul_falls_back_for_dtype_mismatch(monkeypatch): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(200, 200), dtype=np.float32, chunks=(100, 100), blocks=(50, 50)) + b = blosc2.full(shape=(200, 200), fill_value=2, dtype=np.float64, chunks=(100, 100), blocks=(50, 50)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(100, 100), blocks=(50, 50)) + expected = np.matmul(a[:], b[:]) + + assert calls == [] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +@pytest.mark.parametrize("dtype", [np.complex64, np.complex128]) +def test_matmul_complex_falls_back_to_chunked(monkeypatch, dtype): + old_flag = utils_mod.try_miniexpr + calls = _set_pref_matmul_call_recorder(monkeypatch) + try: + _toggle_miniexpr(True) + a = blosc2.asarray(np.ones((100, 100), dtype=dtype)) + b = blosc2.asarray(np.full((100, 100), 2 + 0j, dtype=dtype)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + c = blosc2.matmul(a, b, chunks=(50, 50), blocks=(25, 25)) + expected = np.matmul(a[:], b[:]) + + assert calls == [] + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + +def test_matmul_fast_path_failure_falls_back(monkeypatch): + old_flag = utils_mod.try_miniexpr + + def failing_set_pref_matmul(self, inputs, fp_accuracy): + raise RuntimeError("boom") + + monkeypatch.setattr(blosc2.NDArray, "_set_pref_matmul", failing_set_pref_matmul) + try: + _toggle_miniexpr(True) + a = blosc2.ones(shape=(200, 200), dtype=np.float64, chunks=(100, 100), blocks=(50, 50)) + b = blosc2.full(shape=(200, 200), fill_value=2, dtype=np.float64, chunks=(100, 100), blocks=(50, 50)) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*encountered in matmul", category=RuntimeWarning) + with pytest.warns(RuntimeWarning, match="falling back to chunked path"): + c = blosc2.matmul(a, b, chunks=(100, 100), blocks=(50, 50)) + expected = np.matmul(a[:], b[:]) + + np.testing.assert_allclose(c[:], expected, rtol=1e-6, atol=1e-6) + finally: + _toggle_miniexpr(old_flag) + + @pytest.mark.parametrize( ("ashape", "achunks", "ablocks"), { diff --git a/tests/ndarray/test_ndarray.py b/tests/ndarray/test_ndarray.py index 8c9b45f7..b557bb65 100644 --- a/tests/ndarray/test_ndarray.py +++ b/tests/ndarray/test_ndarray.py @@ -103,6 +103,18 @@ def test_asarray(a): np.testing.assert_allclose(a, b[:]) +def test_ndarray_info_has_human_sizes(): + array = blosc2.asarray(np.arange(16, dtype=np.int32)) + + items = dict(array.info_items) + assert "(" in items["nbytes"] + assert "(" in items["cbytes"] + + text = repr(array.info) + assert "nbytes" in text + assert "cbytes" in text + + @pytest.mark.parametrize( ("shape", "newshape", "chunks", "blocks"), [ diff --git a/tests/ndarray/test_setitem.py b/tests/ndarray/test_setitem.py index 02a0a336..bde27317 100644 --- a/tests/ndarray/test_setitem.py +++ b/tests/ndarray/test_setitem.py @@ -66,7 +66,8 @@ def test_setitem_torch_proxy(shape, chunks, blocks, slices, dtype): dtype_ = {np.float32: torch.float32, np.int32: torch.int32, np.float64: torch.float64}[dtype] val = torch.ones(slice_shape, dtype=dtype_) a[slices] = val - nparray[slices] = val + # Make the expected assignment explicit so NumPy does not rely on torch.__array__(). + nparray[slices] = val.numpy() np.testing.assert_almost_equal(a[...], nparray) diff --git a/tests/test_batch_store.py b/tests/test_batch_store.py new file mode 100644 index 00000000..9ae83de5 --- /dev/null +++ b/tests/test_batch_store.py @@ -0,0 +1,722 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +import pytest + +import blosc2 +from blosc2._msgpack_utils import msgpack_packb, msgpack_unpackb + +BATCHES = [ + [b"bytes\x00payload", "plain text", 42], + [{"nested": [1, 2]}, None, {"tail": True}], + [(1, 2, "three"), 3.5, True], +] + + +def _make_payload(seed, size): + base = bytes((seed + i) % 251 for i in range(251)) + reps = size // len(base) + 1 + return (base * reps)[:size] + + +def _storage(contiguous, urlpath, mode="w"): + return blosc2.Storage(contiguous=contiguous, urlpath=urlpath, mode=mode) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_batchstore.b2b"), + (False, "test_batchstore_s.b2b"), + ], +) +def test_batchstore_roundtrip(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(storage=_storage(contiguous, urlpath)) + assert barray.meta["batchstore"]["serializer"] == "msgpack" + + for i, batch in enumerate(BATCHES, start=1): + assert barray.append(batch) == i + + assert len(barray) == len(BATCHES) + assert barray.max_blocksize is not None + assert 1 <= barray.max_blocksize <= len(BATCHES[0]) + assert [batch[:] for batch in barray] == BATCHES + assert barray.append([1, 2]) == len(BATCHES) + 1 + assert [batch[:] for batch in barray][-1] == [1, 2] + + batch0 = barray[0] + assert isinstance(batch0, blosc2.Batch) + assert len(batch0) == len(BATCHES[0]) + assert batch0[1] == BATCHES[0][1] + assert batch0[:] == BATCHES[0] + assert isinstance(batch0.lazybatch, bytes) + assert batch0.nbytes > 0 + assert batch0.cbytes > 0 + assert batch0.cratio > 0 + + expected = list(BATCHES) + expected.append([1, 2]) + expected[1] = ["updated", {"tuple": (7, 8)}, 99] + expected[-1] = ["tiny", False, "x"] + barray[1] = expected[1] + barray[-1] = expected[-1] + assert barray.insert(0, ["head", 0, "x"]) == len(expected) + 1 + expected.insert(0, ["head", 0, "x"]) + assert barray.insert(-1, ["between", {"k": 5}, None]) == len(expected) + 1 + expected.insert(-1, ["between", {"k": 5}, None]) + assert barray.insert(999, ["tail", 1, 2]) == len(expected) + 1 + expected.insert(999, ["tail", 1, 2]) + assert barray.delete(2) == len(expected) - 1 + del expected[2] + del barray[-2] + del expected[-2] + assert [batch[:] for batch in barray] == expected + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert isinstance(reopened, blosc2.BatchStore) + assert reopened.max_blocksize == barray.max_blocksize + assert [batch[:] for batch in reopened] == expected + with pytest.raises(ValueError): + reopened.append(["nope"]) + with pytest.raises(ValueError): + reopened[0] = ["nope"] + with pytest.raises(ValueError): + reopened.insert(0, ["nope"]) + with pytest.raises(ValueError): + reopened.delete(0) + with pytest.raises(ValueError): + del reopened[0] + with pytest.raises(ValueError): + reopened.extend([["nope"]]) + with pytest.raises(ValueError): + reopened.pop() + with pytest.raises(ValueError): + reopened.clear() + + reopened_rw = blosc2.open(urlpath, mode="a") + reopened_rw[0] = ["changed", "batch", 0] + expected[0] = ["changed", "batch", 0] + assert [batch[:] for batch in reopened_rw] == expected + + if contiguous: + reopened_mmap = blosc2.open(urlpath, mode="r", mmap_mode="r") + assert isinstance(reopened_mmap, blosc2.BatchStore) + assert [batch[:] for batch in reopened_mmap] == expected + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_arrow_ipc_roundtrip(): + pa = pytest.importorskip("pyarrow") + urlpath = "test_batchstore_arrow_ipc.b2b" + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(storage=_storage(True, urlpath), serializer="arrow") + assert barray.serializer == "arrow" + assert barray.meta["batchstore"]["serializer"] == "arrow" + + batch1 = pa.array([[1, 2], None, [3]]) + batch2 = pa.array([[4], [5, 6]]) + barray.append(batch1) + barray.append(batch2) + + assert barray[0][:] == [[1, 2], None, [3]] + assert barray[1][:] == [[4], [5, 6]] + assert barray.meta["batchstore"]["arrow_schema"] is not None + + reopened = blosc2.open(urlpath, mode="r") + assert isinstance(reopened, blosc2.BatchStore) + assert reopened.serializer == "arrow" + assert reopened.meta["batchstore"]["serializer"] == "arrow" + assert reopened[0][:] == [[1, 2], None, [3]] + assert reopened[1][:] == [[4], [5, 6]] + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_inferred_layout_preserves_user_vlmeta(): + barray = blosc2.BatchStore() + barray.vlmeta["user"] = {"x": 1} + + barray.append([1, 2, 3]) + + assert barray.vlmeta["user"] == {"x": 1} + + +def test_batchstore_arrow_layout_persistence_preserves_user_vlmeta(): + pa = pytest.importorskip("pyarrow") + + barray = blosc2.BatchStore(serializer="arrow") + barray.vlmeta["user"] = {"x": 1} + + barray.append(pa.array([[1], [2, 3]])) + + assert barray.vlmeta["user"] == {"x": 1} + + +def test_batchstore_from_cframe(): + barray = blosc2.BatchStore() + barray.extend(BATCHES) + barray.insert(1, ["inserted", True, None]) + del barray[3] + expected = list(BATCHES) + expected.insert(1, ["inserted", True, None]) + del expected[3] + + restored = blosc2.from_cframe(barray.to_cframe()) + assert isinstance(restored, blosc2.BatchStore) + assert [batch[:] for batch in restored] == expected + + restored2 = blosc2.from_cframe(barray.to_cframe()) + assert isinstance(restored2, blosc2.BatchStore) + assert [batch[:] for batch in restored2] == expected + + +def test_batchstore_info(): + barray = blosc2.BatchStore() + barray.extend(BATCHES) + + assert barray.typesize == 1 + assert barray.contiguous == barray.schunk.contiguous + assert barray.urlpath == barray.schunk.urlpath + + items = dict(barray.info_items) + assert items["type"] == "BatchStore" + assert items["serializer"] == "msgpack" + assert items["nbatches"].startswith(f"{len(BATCHES)} (items per batch: mean=") + assert items["nblocks"].startswith(str(len(BATCHES))) + assert items["nitems"] == sum(len(batch) for batch in BATCHES) + assert "urlpath" not in items + assert "contiguous" not in items + assert "typesize" not in items + assert "(" in items["nbytes"] + assert "(" in items["cbytes"] + assert "B)" in items["nbytes"] or "KiB)" in items["nbytes"] or "MiB)" in items["nbytes"] + + text = repr(barray.info) + assert "type" in text + assert "serializer" in text + assert "BatchStore" in text + assert "items per batch" in text + assert "items per block" in text + + +def test_batchstore_info_uses_persisted_batch_lengths(): + barray = blosc2.BatchStore() + barray.extend(BATCHES) + + assert barray.vlmeta["_batch_store_metadata"]["batch_lengths"] == [len(batch) for batch in BATCHES] + + def fail_decode(*args, **kwargs): + raise AssertionError( + "info() should not deserialize batches when batch_lengths metadata is available" + ) + + original_decode_blocks = barray._decode_blocks + barray._decode_blocks = fail_decode + try: + items = dict(barray.info_items) + finally: + barray._decode_blocks = original_decode_blocks + + assert items["nitems"] == sum(len(batch) for batch in BATCHES) + assert "items per batch: mean=" in items["nbatches"] + + +def test_batchstore_info_reports_exact_block_stats_from_lazy_chunks(): + barray = blosc2.BatchStore(max_blocksize=2) + barray.extend([[1, 2, 3, 4, 5], [6, 7], [8]]) + + items = dict(barray.info_items) + assert items["nblocks"] == "5 (items per block: mean=1.60, max=2, min=1)" + + +def test_batchstore_pop_keeps_batch_lengths_metadata_in_sync(): + barray = blosc2.BatchStore(max_blocksize=2) + barray.extend([[1, 2, 3], [4, 5], [6]]) + + removed = barray.pop(1) + + assert removed == [4, 5] + assert [batch[:] for batch in barray] == [[1, 2, 3], [6]] + assert barray.vlmeta["_batch_store_metadata"]["batch_lengths"] == [3, 1] + items = dict(barray.info_items) + assert items["nbatches"].startswith("2 (items per batch: mean=2.00") + + +def test_batchstore_clear_keeps_empty_store_vlmeta_readable(): + urlpath = "test_batchstore_clear_empty_vlmeta.b2b" + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(urlpath=urlpath, mode="w", contiguous=True) + barray.append([1, 2, 3]) + barray.clear() + + assert barray.vlmeta.getall() == {} + + reopened = blosc2.open(urlpath, mode="r") + assert reopened.vlmeta.getall() == {} + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_delete_last_keeps_empty_store_vlmeta_readable(): + urlpath = "test_batchstore_delete_last_empty_vlmeta.b2b" + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(urlpath=urlpath, mode="w", contiguous=True) + barray.append([1, 2, 3]) + barray.delete(0) + + assert barray.vlmeta.getall() == {} + + reopened = blosc2.open(urlpath, mode="r") + assert reopened.vlmeta.getall() == {} + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_zstd_does_not_use_dict_by_default(): + barray = blosc2.BatchStore() + assert barray.cparams.codec == blosc2.Codec.ZSTD + assert barray.cparams.use_dict is False + + +def test_batchstore_explicit_max_blocksize(): + barray = blosc2.BatchStore(max_blocksize=2) + assert barray.max_blocksize == 2 + barray.append([1, 2, 3]) + barray.append([4]) + assert [batch[:] for batch in barray] == [[1, 2, 3], [4]] + + +def test_batchstore_get_vlblock_and_scalar_access(): + urlpath = "test_batchstore_vlblock.b2b" + blosc2.remove_urlpath(urlpath) + + batch = [0, 1, 2, 3, 4] + barray = blosc2.BatchStore(storage=_storage(True, urlpath), max_blocksize=2) + barray.append(batch) + + assert barray.max_blocksize == 2 + assert msgpack_unpackb(barray.schunk.get_vlblock(0, 0)) == batch[:2] + assert msgpack_unpackb(barray.schunk.get_vlblock(0, 1)) == batch[2:4] + assert msgpack_unpackb(barray.schunk.get_vlblock(0, 2)) == batch[4:] + + assert barray[0][0] == 0 + assert barray[0][2] == 2 + assert barray[0][4] == 4 + + reopened = blosc2.open(urlpath, mode="r") + assert isinstance(reopened, blosc2.BatchStore) + assert reopened.max_blocksize == 2 + assert reopened[0][0] == 0 + assert reopened[0][2] == 2 + assert reopened[0][4] == 4 + assert msgpack_unpackb(reopened.schunk.get_vlblock(0, 1)) == batch[2:4] + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_scalar_reads_cache_vlblocks(): + barray = blosc2.BatchStore(max_blocksize=2) + barray.append([0, 1, 2, 3, 4]) + + batch = barray[0] + original_get_vlblock = barray.schunk.get_vlblock + calls = [] + + def wrapped_get_vlblock(nchunk, nblock): + calls.append((nchunk, nblock)) + return original_get_vlblock(nchunk, nblock) + + barray.schunk.get_vlblock = wrapped_get_vlblock + try: + assert batch[0] == 0 + assert batch[1] == 1 + assert batch[0] == 0 + assert batch[2] == 2 + assert batch[3] == 3 + assert calls == [(0, 0), (0, 1)] + finally: + barray.schunk.get_vlblock = original_get_vlblock + + +def test_batchstore_iter_items(): + barray = blosc2.BatchStore(max_blocksize=2) + batches = [[1, 2, 3], [4], [5, 6]] + barray.extend(batches) + + assert [batch[:] for batch in barray] == batches + assert list(barray.iter_items()) == [1, 2, 3, 4, 5, 6] + + +def test_batchstore_respects_explicit_use_dict_and_non_zstd(): + barray = blosc2.BatchStore(cparams={"codec": blosc2.Codec.LZ4, "clevel": 5}) + assert barray.cparams.codec == blosc2.Codec.LZ4 + assert barray.cparams.use_dict is False + + barray = blosc2.BatchStore(cparams={"codec": blosc2.Codec.LZ4HC, "clevel": 1, "use_dict": True}) + assert barray.cparams.codec == blosc2.Codec.LZ4HC + assert barray.cparams.use_dict is True + + barray = blosc2.BatchStore(cparams={"codec": blosc2.Codec.ZSTD, "clevel": 0}) + assert barray.cparams.codec == blosc2.Codec.ZSTD + assert barray.cparams.use_dict is False + + barray = blosc2.BatchStore(cparams={"codec": blosc2.Codec.ZSTD, "clevel": 5, "use_dict": False}) + assert barray.cparams.use_dict is False + + barray = blosc2.BatchStore(cparams=blosc2.CParams(codec=blosc2.Codec.ZSTD, clevel=5, use_dict=False)) + assert barray.cparams.use_dict is False + + +def test_batchstore_guess_max_blocksize_uses_l2_for_clevel_5(monkeypatch): + monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 100) + monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 1000) + barray = blosc2.BatchStore(cparams={"clevel": 5}) + assert barray._guess_blocksize([30, 30, 30, 30]) == 4 + + +def test_batchstore_guess_max_blocksize_uses_l2_for_mid_clevel(monkeypatch): + monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 100) + monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 150) + barray = blosc2.BatchStore(cparams={"clevel": 6}) + assert barray._guess_blocksize([60, 60, 60, 60]) == 2 + + +def test_batchstore_guess_max_blocksize_uses_full_batch_for_clevel_9(monkeypatch): + monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 1) + monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 1) + barray = blosc2.BatchStore(cparams={"clevel": 9}) + assert barray._guess_blocksize([100, 100, 100, 100]) == 4 + + +def test_vlcompress_small_blocks_roundtrip(): + values = [ + {"value": None}, + {"value": []}, + {"value": []}, + {"value": ["en:salt"]}, + {"value": []}, + {"value": ["en:sugar", "en:flour"]}, + {"value": None}, + {"value": []}, + {"value": ["en:water", "en:yeast", "en:oil"]}, + {"value": []}, + {"value": []}, + {"value": ["en:acid", "en:color", "en:preservative", "en:spice"]}, + {"value": None}, + {"value": []}, + {"value": ["en:a", "en:b", "en:c", "en:d", "en:e", "en:f"]}, + {"value": []}, + {"value": []}, + {"value": None}, + {"value": ["en:x"]}, + {"value": []}, + ] + payloads = [msgpack_packb(value) for value in values] + + batch_payload = blosc2.blosc2_ext.vlcompress( + payloads, + codec=blosc2.Codec.ZSTD, + clevel=5, + typesize=1, + nthreads=1, + ) + out = blosc2.blosc2_ext.vldecompress(batch_payload, nthreads=1) + + assert out == payloads + + +def test_batchstore_constructor_kwargs(): + urlpath = "test_batchstore_kwargs.b2b" + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(urlpath=urlpath, mode="w", contiguous=True) + barray.extend(BATCHES) + + reopened = blosc2.BatchStore(urlpath=urlpath, mode="r", contiguous=True, mmap_mode="r") + assert [batch[:] for batch in reopened] == BATCHES + + blosc2.remove_urlpath(urlpath) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_batchstore_list_ops.b2b"), + (False, "test_batchstore_list_ops_s.b2b"), + ], +) +def test_batchstore_list_like_ops(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + barray = blosc2.BatchStore(storage=_storage(contiguous, urlpath)) + barray.extend([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert [batch[:] for batch in barray] == [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + assert barray.pop() == [7, 8, 9] + assert barray.pop(0) == [1, 2, 3] + assert [batch[:] for batch in barray] == [[4, 5, 6]] + + barray.clear() + assert len(barray) == 0 + assert [batch[:] for batch in barray] == [] + + barray.extend([["a", "b", "c"], ["d", "e", "f"]]) + assert [batch[:] for batch in barray] == [["a", "b", "c"], ["d", "e", "f"]] + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert [batch[:] for batch in reopened] == [["a", "b", "c"], ["d", "e", "f"]] + + blosc2.remove_urlpath(urlpath) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_batchstore_slices.b2b"), + (False, "test_batchstore_slices_s.b2b"), + ], +) +def test_batchstore_slices(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + expected = [[i, i + 100, i + 200] for i in range(8)] + barray = blosc2.BatchStore(storage=_storage(contiguous, urlpath)) + barray.extend(expected) + + assert [batch[:] for batch in barray[1:6:2]] == expected[1:6:2] + assert [batch[:] for batch in barray[::-2]] == expected[::-2] + + barray[2:5] = [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]] + expected[2:5] = [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]] + assert [batch[:] for batch in barray] == expected + + barray[1:6:2] = [[100, 101, 102], [103, 104, 105], [106, 107, 108]] + expected[1:6:2] = [[100, 101, 102], [103, 104, 105], [106, 107, 108]] + assert [batch[:] for batch in barray] == expected + + del barray[::3] + del expected[::3] + assert [batch[:] for batch in barray] == expected + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert [batch[:] for batch in reopened[::2]] == expected[::2] + with pytest.raises(ValueError): + reopened[1:3] = [[9]] + with pytest.raises(ValueError): + del reopened[::2] + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_slice_errors(): + barray = blosc2.BatchStore() + barray.extend([[0], [1], [2], [3]]) + + with pytest.raises(ValueError, match="extended slice"): + barray[::2] = [[9]] + with pytest.raises(TypeError): + barray[1:2] = 3 + with pytest.raises(ValueError): + _ = barray[::0] + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_batchstore_items.b2b"), + (False, "test_batchstore_items_s.b2b"), + ], +) +def test_batchstore_items_accessor(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + batches = [["a", "b"], [10, 11, 12], [{"x": 1}], [None, True]] + flat = [item for batch in batches for item in batch] + barray = blosc2.BatchStore(storage=_storage(contiguous, urlpath), max_blocksize=2) + barray.extend(batches) + + assert len(barray.items) == len(flat) + assert barray.items[0] == flat[0] + assert barray.items[3] == flat[3] + assert barray.items[-1] == flat[-1] + assert barray.items[1:6] == flat[1:6] + assert barray.items[::-2] == flat[::-2] + + barray.append(["tail0", "tail1"]) + flat.extend(["tail0", "tail1"]) + assert len(barray.items) == len(flat) + assert barray.items[-2:] == flat[-2:] + + barray.insert(1, ["mid0", "mid1"]) + flat[2:2] = ["mid0", "mid1"] + assert barray.items[:] == flat + + barray[2] = ["replaced"] + batch_start = len(batches[0]) + 2 + flat[batch_start : batch_start + 3] = ["replaced"] + assert barray.items[:] == flat + + del barray[0] + del flat[:2] + assert barray.items[:] == flat + + with pytest.raises(IndexError, match="item index out of range"): + _ = barray.items[len(flat)] + with pytest.raises(TypeError, match="item indices must be integers"): + _ = barray.items[1.5] + with pytest.raises(ValueError): + _ = barray.items[::0] + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert reopened.items[:] == flat + assert reopened.items[2] == flat[2] + + blosc2.remove_urlpath(urlpath) + + +def test_batchstore_copy(): + urlpath = "test_batchstore_copy.b2b" + copy_path = "test_batchstore_copy_out.b2b" + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + original = blosc2.BatchStore(urlpath=urlpath, mode="w", contiguous=True) + original.extend(BATCHES) + original.insert(1, ["copy", True, 123]) + + copied = original.copy( + urlpath=copy_path, contiguous=False, cparams={"codec": blosc2.Codec.LZ4, "clevel": 5} + ) + assert [batch[:] for batch in copied] == [batch[:] for batch in original] + assert copied.urlpath == copy_path + assert copied.schunk.contiguous is False + assert copied.cparams.codec == blosc2.Codec.LZ4 + assert copied.cparams.clevel == 5 + + inmem = original.copy() + assert [batch[:] for batch in inmem] == [batch[:] for batch in original] + assert inmem.urlpath is None + + with pytest.raises(ValueError, match="meta should not be passed to copy"): + original.copy(meta={}) + + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + +def test_batchstore_copy_with_storage_preserves_user_metadata(): + urlpath = "test_batchstore_copy_storage.b2b" + copy_path = "test_batchstore_copy_storage_out.b2b" + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + original = blosc2.BatchStore(urlpath=urlpath, mode="w", contiguous=True, meta={"user_meta": {"a": 1}}) + original.vlmeta["user_vlmeta"] = {"b": 2} + original.extend(BATCHES) + + copied = original.copy(storage=blosc2.Storage(contiguous=False, urlpath=copy_path, mode="w")) + + assert [batch[:] for batch in copied] == [batch[:] for batch in original] + assert copied.meta["user_meta"] == {"a": 1} + assert copied.vlmeta["user_vlmeta"] == {"b": 2} + + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + +@pytest.mark.parametrize(("contiguous", "nthreads"), [(False, 2), (True, 4)]) +def test_batchstore_multithreaded_inner_vl(contiguous, nthreads): + batches = [] + for batch_id in range(24): + batch = [] + for obj_id, size in enumerate( + (13, 1024 + batch_id * 17, 70_000 + batch_id * 13, 250_000 + batch_id * 101) + ): + batch.append( + { + "batch": batch_id, + "obj": obj_id, + "size": size, + "payload": _make_payload(batch_id + obj_id, size), + } + ) + batches.append(batch) + + barray = blosc2.BatchStore( + storage=blosc2.Storage(contiguous=contiguous), + cparams=blosc2.CParams(typesize=1, nthreads=nthreads, codec=blosc2.Codec.ZSTD, clevel=5), + dparams=blosc2.DParams(nthreads=nthreads), + ) + barray.extend(batches) + + assert [batch[:] for batch in barray] == batches + assert [barray[i][:] for i in range(len(barray))] == batches + + +def test_batchstore_validation_errors(): + barray = blosc2.BatchStore() + + with pytest.raises(TypeError): + barray.append("value") + with pytest.raises(ValueError): + barray.append([]) + with pytest.raises(TypeError): + barray.insert("0", ["bad"]) + with pytest.raises(IndexError): + barray.delete(3) + with pytest.raises(IndexError): + blosc2.BatchStore().pop() + barray.extend([[1, 2, 3]]) + assert barray.append([2, 3]) == 2 + assert [batch[:] for batch in barray] == [[1, 2, 3], [2, 3]] + with pytest.raises(NotImplementedError): + barray.pop(slice(0, 1)) + + +def test_batchstore_in_embed_store(): + estore = blosc2.EmbedStore() + barray = blosc2.BatchStore() + barray.extend(BATCHES) + + estore["/batch"] = barray + restored = estore["/batch"] + assert isinstance(restored, blosc2.BatchStore) + assert [batch[:] for batch in restored] == BATCHES + + +def test_batchstore_in_dict_store(): + path = "test_batchstore_store.b2z" + blosc2.remove_urlpath(path) + + with blosc2.DictStore(path, mode="w", threshold=1) as dstore: + barray = blosc2.BatchStore() + barray.extend(BATCHES) + dstore["/batch"] = barray + + with blosc2.DictStore(path, mode="r") as dstore: + restored = dstore["/batch"] + assert isinstance(restored, blosc2.BatchStore) + assert [batch[:] for batch in restored] == BATCHES + + blosc2.remove_urlpath(path) diff --git a/tests/test_dict_store.py b/tests/test_dict_store.py index f18b006d..337ace30 100644 --- a/tests/test_dict_store.py +++ b/tests/test_dict_store.py @@ -16,6 +16,22 @@ from blosc2.dict_store import DictStore +def _rename_store_member(store_path, old_name, new_name): + """Rename an external leaf inside a .b2d/.b2z store without changing its contents.""" + if str(store_path).endswith(".b2d"): + old_path = os.path.join(store_path, old_name.replace("/", os.sep)) + new_path = os.path.join(store_path, new_name.replace("/", os.sep)) + os.rename(old_path, new_path) + return + + tmp_zip = f"{store_path}.tmp" + with zipfile.ZipFile(store_path, "r") as src, zipfile.ZipFile(tmp_zip, "w", zipfile.ZIP_STORED) as dst: + for info in src.infolist(): + arcname = new_name if info.filename == old_name else info.filename + dst.writestr(arcname, src.read(info.filename), compress_type=zipfile.ZIP_STORED) + os.replace(tmp_zip, store_path) + + @pytest.fixture(params=["b2d", "b2z"]) def populated_dict_store(request): """Create and populate a DictStore for tests. @@ -98,6 +114,74 @@ def test_to_b2z_and_reopen(populated_dict_store): assert np.all(dstore_read["/nodeB"][:] == np.arange(6)) +def test_to_b2z_from_readonly_b2d(): + b2d_path = "test_to_b2z_from_readonly.b2d" + b2z_path = "test_to_b2z_from_readonly.b2z" + + if os.path.exists(b2d_path): + shutil.rmtree(b2d_path) + if os.path.exists(b2z_path): + os.remove(b2z_path) + + with DictStore(b2d_path, mode="w") as dstore: + dstore["/nodeA"] = np.arange(5) + dstore["/nodeB"] = np.arange(6) + + with DictStore(b2d_path, mode="r") as dstore: + packed = dstore.to_b2z(filename=b2z_path) + assert packed.endswith(b2z_path) + + with DictStore(b2z_path, mode="r") as dstore: + assert np.all(dstore["/nodeA"][:] == np.arange(5)) + assert np.all(dstore["/nodeB"][:] == np.arange(6)) + + shutil.rmtree(b2d_path) + os.remove(b2z_path) + + +def test_to_b2z_accepts_positional_filename(): + b2d_path = "test_to_b2z_positional_filename.b2d" + b2z_path = "test_to_b2z_positional_filename.b2z" + + if os.path.exists(b2d_path): + shutil.rmtree(b2d_path) + if os.path.exists(b2z_path): + os.remove(b2z_path) + + with DictStore(b2d_path, mode="w") as dstore: + dstore["/nodeA"] = np.arange(5) + + with DictStore(b2d_path, mode="r") as dstore: + packed = dstore.to_b2z(b2z_path) + assert packed.endswith(b2z_path) + + with DictStore(b2z_path, mode="r") as dstore: + assert np.all(dstore["/nodeA"][:] == np.arange(5)) + + shutil.rmtree(b2d_path) + os.remove(b2z_path) + + +def test_to_b2z_from_readonly_b2z_raises(): + b2z_path = "test_to_b2z_readonly_zip.b2z" + out_path = "test_to_b2z_readonly_zip_out.b2z" + + for path in (b2z_path, out_path): + if os.path.exists(path): + os.remove(path) + + with DictStore(b2z_path, mode="w") as dstore: + dstore["/nodeA"] = np.arange(5) + + with ( + DictStore(b2z_path, mode="r") as dstore, + pytest.raises(ValueError, match=r"\.b2z DictStore opened in read mode"), + ): + dstore.to_b2z(filename=out_path) + + os.remove(b2z_path) + + def test_map_tree_precedence(populated_dict_store): dstore, path = populated_dict_store # Create external file and add to dstore @@ -223,6 +307,121 @@ def test_external_schunk_file_and_reopen(): os.remove(path) +def test_store_and_retrieve_vlarray_in_dict(tmp_path): + path = tmp_path / "test_dstore_vlarray_embed.b2z" + values = [{"name": "alpha", "count": 1}, None, ("tuple", 2), [1, "two", b"three"]] + + vlarray = blosc2.VLArray() + vlarray.extend(values) + + with DictStore(str(path), mode="w") as dstore: + dstore["/vlarray"] = vlarray + value = dstore["/vlarray"] + assert isinstance(value, blosc2.VLArray) + assert list(value) == values + + with DictStore(str(path), mode="r") as dstore_read: + value = dstore_read["/vlarray"] + assert isinstance(value, blosc2.VLArray) + assert list(value) == values + + +def test_external_vlarray_file_and_reopen(tmp_path): + ext_path = tmp_path / "ext_vlarray.b2frame" + path = tmp_path / "test_dstore_vlarray_external.b2z" + values = ["alpha", {"nested": True}, None, (1, 2, 3)] + + vlarray = blosc2.VLArray(urlpath=str(ext_path), mode="w", contiguous=True) + vlarray.extend(values) + vlarray.vlmeta["description"] = "External VLArray" + + with DictStore(str(path), mode="w", threshold=None) as dstore: + dstore["/dir1/vlarray_ext"] = vlarray + assert "/dir1/vlarray_ext" in dstore.map_tree + assert dstore.map_tree["/dir1/vlarray_ext"].endswith(".b2f") + + with zipfile.ZipFile(path, "r") as zf: + assert "dir1/vlarray_ext.b2f" in zf.namelist() + + with DictStore(str(path), mode="r") as dstore_read: + value = dstore_read["/dir1/vlarray_ext"] + assert isinstance(value, blosc2.VLArray) + assert list(value) == values + assert value.vlmeta["description"] == "External VLArray" + + +@pytest.mark.parametrize("storage_type", ["b2d", "b2z"]) +def test_metadata_discovery_reopens_renamed_external_ndarray(storage_type, tmp_path): + path = tmp_path / f"test_renamed_ndarray.{storage_type}" + ext_path = tmp_path / "renamed_array_source.b2nd" + + with DictStore(str(path), mode="w", threshold=None) as dstore: + arr_external = blosc2.arange(5, urlpath=str(ext_path), mode="w") + arr_external.vlmeta["description"] = "Renamed NDArray" + dstore["/dir1/node3"] = arr_external + + old_name = "dir1/node3.b2nd" + new_name = "dir1/node3.weird" + _rename_store_member(str(path), old_name, new_name) + + with pytest.warns(UserWarning, match=r"node3\.weird'.*NDArray.*expected '\.b2nd'"): + dstore_read = DictStore(str(path), mode="r") + with dstore_read: + assert dstore_read.map_tree["/dir1/node3"] == new_name + node3 = dstore_read["/dir1/node3"] + assert isinstance(node3, blosc2.NDArray) + assert np.array_equal(node3[:], np.arange(5)) + assert node3.vlmeta["description"] == "Renamed NDArray" + + +@pytest.mark.parametrize("storage_type", ["b2d", "b2z"]) +def test_metadata_discovery_reopens_renamed_external_vlarray(storage_type, tmp_path): + path = tmp_path / f"test_renamed_vlarray.{storage_type}" + ext_path = tmp_path / "renamed_vlarray_source.b2frame" + values = ["alpha", {"nested": True}, None, (1, 2, 3)] + + vlarray = blosc2.VLArray(urlpath=str(ext_path), mode="w", contiguous=True) + vlarray.extend(values) + vlarray.vlmeta["description"] = "Renamed VLArray" + + with DictStore(str(path), mode="w", threshold=None) as dstore: + dstore["/dir1/vlarray_ext"] = vlarray + + old_name = "dir1/vlarray_ext.b2f" + new_name = "dir1/vlarray_ext.renamed" + _rename_store_member(str(path), old_name, new_name) + + with pytest.warns(UserWarning, match=r"vlarray_ext\.renamed'.*VLArray.*expected '\.b2f'"): + dstore_read = DictStore(str(path), mode="r") + with dstore_read: + assert dstore_read.map_tree["/dir1/vlarray_ext"] == new_name + value = dstore_read["/dir1/vlarray_ext"] + assert isinstance(value, blosc2.VLArray) + assert list(value) == values + assert value.vlmeta["description"] == "Renamed VLArray" + + +def test_metadata_discovery_warns_and_skips_unsupported_blosc2_leaf(tmp_path): + path = tmp_path / "test_unsupported_lazyexpr.b2d" + + with DictStore(str(path), mode="w") as dstore: + dstore["/embedded"] = np.arange(3) + + a = blosc2.asarray(np.arange(5), urlpath=str(tmp_path / "a.b2nd"), mode="w") + b = blosc2.asarray(np.arange(5), urlpath=str(tmp_path / "b.b2nd"), mode="w") + expr = a + b + expr_path = path / "unsupported_lazyexpr.b2nd" + expr.save(str(expr_path)) + + with pytest.warns( + UserWarning, match=r"Ignoring unsupported Blosc2 object.*unsupported_lazyexpr\.b2nd.*LazyExpr" + ): + dstore_read = DictStore(str(path), mode="r") + with dstore_read: + assert "/unsupported_lazyexpr" not in dstore_read + assert "/embedded" in dstore_read + + def _digest_value(value): """Return a bytes digest of a stored value.""" if isinstance(value, blosc2.SChunk): diff --git a/tests/test_schunk.py b/tests/test_schunk.py index 539e495f..db7c087e 100644 --- a/tests/test_schunk.py +++ b/tests/test_schunk.py @@ -186,6 +186,19 @@ def test_schunk(contiguous, urlpath, mode, mmap_mode, nbytes, cparams, dparams, blosc2.remove_urlpath(urlpath) +def test_schunk_info_has_human_sizes(): + schunk = blosc2.SChunk(chunksize=32) + schunk.append_data(b"a" * 32) + + items = dict(schunk.info_items) + assert "(" in items["nbytes"] + assert "(" in items["cbytes"] + + text = repr(schunk.info) + assert "nbytes" in text + assert "cbytes" in text + + @pytest.mark.parametrize( ("urlpath", "contiguous", "mode", "mmap_mode"), [ diff --git a/tests/test_schunk_update.py b/tests/test_schunk_update.py index 2f3a8b7b..4115c6b0 100644 --- a/tests/test_schunk_update.py +++ b/tests/test_schunk_update.py @@ -108,3 +108,38 @@ def test_update(contiguous, urlpath, nchunks, nupdates, copy, create_chunk, gil) for i in range(nchunks): schunk.decompress_chunk(i) blosc2.remove_urlpath(urlpath) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_variable_append_chunk.b2frame"), + (False, "test_variable_append_chunk_s.b2frame"), + ], +) +def test_append_chunk_variable_sizes(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + schunk = blosc2.SChunk(chunksize=-1, contiguous=contiguous, urlpath=urlpath, cparams={"typesize": 1}) + payloads = [b"a" * 13, b"b" * 29, b"c" * 41] + + for i, payload in enumerate(payloads, start=1): + chunk = blosc2.compress2(payload, typesize=1) + assert schunk.append_chunk(chunk) == i + assert schunk.decompress_chunk(i - 1) == payload + + assert schunk.chunksize == 0 + + replacement = b"z" * 17 + schunk.update_chunk(1, blosc2.compress2(replacement, typesize=1)) + expected = [payloads[0], replacement, payloads[2]] + assert [schunk.decompress_chunk(i) for i in range(schunk.nchunks)] == expected + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert reopened.chunksize == 0 + assert [reopened.decompress_chunk(i) for i in range(reopened.nchunks)] == expected + + blosc2.remove_urlpath(urlpath) diff --git a/tests/test_tree_store.py b/tests/test_tree_store.py index 49e26d71..780d6cf5 100644 --- a/tests/test_tree_store.py +++ b/tests/test_tree_store.py @@ -7,6 +7,7 @@ import os import shutil +import zipfile import numpy as np import pytest @@ -15,6 +16,22 @@ from blosc2.tree_store import TreeStore +def _rename_store_member(store_path, old_name, new_name): + """Rename an external leaf inside a .b2d/.b2z store without changing its contents.""" + if str(store_path).endswith(".b2d"): + old_path = os.path.join(store_path, old_name.replace("/", os.sep)) + new_path = os.path.join(store_path, new_name.replace("/", os.sep)) + os.rename(old_path, new_path) + return + + tmp_zip = f"{store_path}.tmp" + with zipfile.ZipFile(store_path, "r") as src, zipfile.ZipFile(tmp_zip, "w", zipfile.ZIP_STORED) as dst: + for info in src.infolist(): + arcname = new_name if info.filename == old_name else info.filename + dst.writestr(arcname, src.read(info.filename), compress_type=zipfile.ZIP_STORED) + os.replace(tmp_zip, store_path) + + @pytest.fixture(params=["b2d", "b2z"]) def populated_tree_store(request): """A fixture that creates and populates a TreeStore.""" @@ -604,6 +621,106 @@ def test_schunk_support(): os.remove("test_schunk.b2z") +def test_vlarray_support(): + """Test that TreeStore supports embedded VLArray objects.""" + values = [{"name": "alpha", "count": 1}, None, ("tuple", 2), [1, "two", b"three"]] + with TreeStore("test_vlarray.b2z", mode="w") as tstore: + vlarray = blosc2.VLArray() + vlarray.extend(values) + tstore["/data/vlarray1"] = vlarray + + retrieved = tstore["/data/vlarray1"] + assert isinstance(retrieved, blosc2.VLArray) + assert list(retrieved) == values + + data_subtree = tstore["/data"] + assert isinstance(data_subtree, TreeStore) + assert set(data_subtree.keys()) == {"/vlarray1"} + + with TreeStore("test_vlarray.b2z", mode="r") as tstore: + retrieved = tstore["/data/vlarray1"] + assert isinstance(retrieved, blosc2.VLArray) + assert list(retrieved) == values + + os.remove("test_vlarray.b2z") + + +def test_external_vlarray_support(): + """Test that TreeStore supports external VLArray objects.""" + ext_path = "ext_vlarray.b2frame" + values = ["alpha", {"nested": True}, None, (1, 2, 3)] + if os.path.exists(ext_path): + os.remove(ext_path) + + vlarray = blosc2.VLArray(urlpath=ext_path, mode="w", contiguous=True) + vlarray.extend(values) + vlarray.vlmeta["description"] = "External VLArray for TreeStore" + + with TreeStore("test_vlarray_external.b2z", mode="w", threshold=None) as tstore: + tstore["/data/vlarray_ext"] = vlarray + assert "/data/vlarray_ext" in tstore + + with TreeStore("test_vlarray_external.b2z", mode="r") as tstore: + retrieved = tstore["/data/vlarray_ext"] + assert isinstance(retrieved, blosc2.VLArray) + assert list(retrieved) == values + assert retrieved.vlmeta["description"] == "External VLArray for TreeStore" + + if os.path.exists(ext_path): + os.remove(ext_path) + os.remove("test_vlarray_external.b2z") + + +def test_external_batchstore_support(tmp_path): + store_path = tmp_path / "test_batchstore_external.b2d" + + with TreeStore(str(store_path), mode="w", threshold=0) as tstore: + bstore = blosc2.BatchStore(max_blocksize=2) + bstore.extend([[{"id": 1}, {"id": 2}], [{"id": 3}]]) + tstore["/data/batchstore"] = bstore + + batchstore_path = store_path / "data" / "batchstore.b2b" + assert batchstore_path.exists() + + with TreeStore(str(store_path), mode="r") as tstore: + retrieved = tstore["/data/batchstore"] + assert isinstance(retrieved, blosc2.BatchStore) + assert [batch[:] for batch in retrieved] == [[{"id": 1}, {"id": 2}], [{"id": 3}]] + + +@pytest.mark.parametrize("storage_type", ["b2d", "b2z"]) +def test_metadata_discovery_reopens_renamed_batchstore_leaf(storage_type, tmp_path): + store_path = tmp_path / f"test_batchstore_renamed.{storage_type}" + + with TreeStore(str(store_path), mode="w", threshold=0) as tstore: + bstore = blosc2.BatchStore(max_blocksize=2) + bstore.extend([[{"id": 1}, {"id": 2}], [{"id": 3}]]) + tstore["/data/batchstore"] = bstore + + old_name = "data/batchstore.b2b" + new_name = "data/batchstore.odd" + _rename_store_member(str(store_path), old_name, new_name) + + with pytest.warns(UserWarning, match=r"batchstore\.odd'.*BatchStore.*expected '\.b2b'"): + tstore = TreeStore(str(store_path), mode="r") + with tstore: + assert tstore.map_tree["/data/batchstore"] == new_name + retrieved = tstore["/data/batchstore"] + assert isinstance(retrieved, blosc2.BatchStore) + assert [batch[:] for batch in retrieved] == [[{"id": 1}, {"id": 2}], [{"id": 3}]] + + +def test_treestore_vlmeta_externalized_b2d(tmp_path): + store_path = tmp_path / "test_vlmeta_externalized.b2d" + + with TreeStore(str(store_path), mode="w", threshold=0) as tstore: + tstore["/data"] = np.array([1, 2, 3]) + tstore.vlmeta["schema_manifest"] = {"version": 1, "fields": {"a": {"kind": "fixed"}}} + + with TreeStore(str(store_path), mode="r") as tstore: + assert tstore.vlmeta["schema_manifest"] == {"version": 1, "fields": {"a": {"kind": "fixed"}}} + + def test_walk_topdown_argument_ordering(): """Ensure walk supports topdown argument mimicking os.walk order semantics.""" with TreeStore("test_walk_topdown.b2z", mode="w") as tstore: diff --git a/tests/test_vlarray.py b/tests/test_vlarray.py new file mode 100644 index 00000000..0c1f01f3 --- /dev/null +++ b/tests/test_vlarray.py @@ -0,0 +1,336 @@ +####################################################################### +# Copyright (c) 2019-present, Blosc Development Team +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +####################################################################### + +import pytest + +import blosc2 + +VALUES = [ + b"bytes\x00payload", + "plain text", + 42, + 3.5, + True, + None, + [1, "two", b"three"], + (1, 2, "three"), + {"nested": [1, 2], "tuple": (3, 4)}, +] + + +def _storage(contiguous, urlpath, mode="w"): + return blosc2.Storage(contiguous=contiguous, urlpath=urlpath, mode=mode) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_vlarray.b2frame"), + (False, "test_vlarray_s.b2frame"), + ], +) +def test_vlarray_roundtrip(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + vlarray = blosc2.VLArray(storage=_storage(contiguous, urlpath)) + assert vlarray.meta["vlarray"]["serializer"] == "msgpack" + + for i, value in enumerate(VALUES, start=1): + assert vlarray.append(value) == i + + assert len(vlarray) == len(VALUES) + assert list(vlarray) == VALUES + assert vlarray[-1] == VALUES[-1] + + expected = list(VALUES) + expected[1] = {"updated": ("tuple", 7)} + expected[-1] = "tiny" + vlarray[1] = expected[1] + vlarray[-1] = expected[-1] + assert vlarray.insert(0, "head") == len(expected) + 1 + expected.insert(0, "head") + assert vlarray.insert(-1, {"between": 5}) == len(expected) + 1 + expected.insert(-1, {"between": 5}) + assert vlarray.insert(999, "tail") == len(expected) + 1 + expected.insert(999, "tail") + assert vlarray.delete(2) == len(expected) - 1 + del expected[2] + del vlarray[-2] + del expected[-2] + assert list(vlarray) == expected + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert isinstance(reopened, blosc2.VLArray) + assert list(reopened) == expected + with pytest.raises(ValueError): + reopened.append("nope") + with pytest.raises(ValueError): + reopened[0] = "nope" + with pytest.raises(ValueError): + reopened.insert(0, "nope") + with pytest.raises(ValueError): + reopened.delete(0) + with pytest.raises(ValueError): + del reopened[0] + with pytest.raises(ValueError): + reopened.extend(["nope"]) + with pytest.raises(ValueError): + reopened.pop() + with pytest.raises(ValueError): + reopened.clear() + + reopened_rw = blosc2.open(urlpath, mode="a") + reopened_rw[0] = "changed" + expected[0] = "changed" + assert list(reopened_rw) == expected + + if contiguous: + reopened_mmap = blosc2.open(urlpath, mode="r", mmap_mode="r") + assert isinstance(reopened_mmap, blosc2.VLArray) + assert list(reopened_mmap) == expected + + blosc2.remove_urlpath(urlpath) + + +def test_vlarray_from_cframe(): + vlarray = blosc2.VLArray() + vlarray.extend(VALUES) + vlarray.insert(1, {"inserted": True}) + del vlarray[3] + expected = list(VALUES) + expected.insert(1, {"inserted": True}) + del expected[3] + + restored = blosc2.from_cframe(vlarray.to_cframe()) + assert isinstance(restored, blosc2.VLArray) + assert list(restored) == expected + + restored2 = blosc2.vlarray_from_cframe(vlarray.to_cframe()) + assert isinstance(restored2, blosc2.VLArray) + assert list(restored2) == expected + + +def test_vlarray_info(): + vlarray = blosc2.VLArray() + vlarray.extend(VALUES) + + assert vlarray.typesize == 1 + assert vlarray.contiguous == vlarray.schunk.contiguous + assert vlarray.urlpath == vlarray.schunk.urlpath + + items = dict(vlarray.info_items) + assert items["type"] == "VLArray" + assert items["entries"] == len(VALUES) + assert items["item_nbytes_min"] > 0 + assert items["item_nbytes_max"] >= items["item_nbytes_min"] + assert items["chunk_cbytes_min"] > 0 + assert items["chunk_cbytes_max"] >= items["chunk_cbytes_min"] + assert "urlpath" not in items + assert "contiguous" not in items + assert "typesize" not in items + assert "(" in items["nbytes"] + assert "(" in items["cbytes"] + + text = repr(vlarray.info) + assert "type" in text + assert "VLArray" in text + assert "item_nbytes_avg" in text + + +def test_vlarray_zstd_uses_dict_by_default(): + vlarray = blosc2.VLArray() + assert vlarray.cparams.codec == blosc2.Codec.ZSTD + assert vlarray.cparams.use_dict is True + + +def test_vlarray_respects_explicit_use_dict_and_non_zstd(): + vlarray = blosc2.VLArray(cparams={"codec": blosc2.Codec.LZ4, "clevel": 5}) + assert vlarray.cparams.codec == blosc2.Codec.LZ4 + assert vlarray.cparams.use_dict is False + + vlarray = blosc2.VLArray(cparams={"codec": blosc2.Codec.LZ4HC, "clevel": 1, "use_dict": True}) + assert vlarray.cparams.codec == blosc2.Codec.LZ4HC + assert vlarray.cparams.use_dict is True + + vlarray = blosc2.VLArray(cparams={"codec": blosc2.Codec.ZSTD, "clevel": 0}) + assert vlarray.cparams.codec == blosc2.Codec.ZSTD + assert vlarray.cparams.use_dict is False + + vlarray = blosc2.VLArray(cparams={"codec": blosc2.Codec.ZSTD, "clevel": 5, "use_dict": False}) + assert vlarray.cparams.use_dict is False + + vlarray = blosc2.VLArray(cparams=blosc2.CParams(codec=blosc2.Codec.ZSTD, clevel=5, use_dict=False)) + assert vlarray.cparams.use_dict is False + + +def test_vlarray_constructor_kwargs(): + urlpath = "test_vlarray_kwargs.b2frame" + blosc2.remove_urlpath(urlpath) + + vlarray = blosc2.VLArray(urlpath=urlpath, mode="w", contiguous=True) + for value in VALUES: + vlarray.append(value) + + reopened = blosc2.VLArray(urlpath=urlpath, mode="r", contiguous=True, mmap_mode="r") + assert list(reopened) == VALUES + + blosc2.remove_urlpath(urlpath) + + +def test_vlarray_size_guard(monkeypatch): + vlarray = blosc2.VLArray() + monkeypatch.setattr(blosc2, "MAX_BUFFERSIZE", 4) + with pytest.raises(ValueError, match="Serialized objects cannot be larger"): + vlarray.append("payload") + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_vlarray_list_ops.b2frame"), + (False, "test_vlarray_list_ops_s.b2frame"), + ], +) +def test_vlarray_list_like_ops(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + vlarray = blosc2.VLArray(storage=_storage(contiguous, urlpath)) + vlarray.extend([1, 2, 3]) + assert list(vlarray) == [1, 2, 3] + assert vlarray.pop() == 3 + assert vlarray.pop(0) == 1 + assert list(vlarray) == [2] + + vlarray.clear() + assert len(vlarray) == 0 + assert list(vlarray) == [] + + vlarray.extend(["a", "b"]) + assert list(vlarray) == ["a", "b"] + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert list(reopened) == ["a", "b"] + + blosc2.remove_urlpath(urlpath) + + +@pytest.mark.parametrize( + ("contiguous", "urlpath"), + [ + (False, None), + (True, None), + (True, "test_vlarray_slices.b2frame"), + (False, "test_vlarray_slices_s.b2frame"), + ], +) +def test_vlarray_slices(contiguous, urlpath): + blosc2.remove_urlpath(urlpath) + + expected = list(range(8)) + vlarray = blosc2.VLArray(storage=_storage(contiguous, urlpath)) + vlarray.extend(expected) + + assert vlarray[1:6:2] == expected[1:6:2] + assert vlarray[::-2] == expected[::-2] + + vlarray[2:5] = ["a", "b"] + expected[2:5] = ["a", "b"] + assert list(vlarray) == expected + + vlarray[1:6:2] = [100, 101, 102] + expected[1:6:2] = [100, 101, 102] + assert list(vlarray) == expected + + del vlarray[::3] + del expected[::3] + assert list(vlarray) == expected + + if urlpath is not None: + reopened = blosc2.open(urlpath, mode="r") + assert reopened[::2] == expected[::2] + with pytest.raises(ValueError): + reopened[1:3] = [9] + with pytest.raises(ValueError): + del reopened[::2] + + blosc2.remove_urlpath(urlpath) + + +def test_vlarray_slice_errors(): + vlarray = blosc2.VLArray() + vlarray.extend([0, 1, 2, 3]) + + with pytest.raises(ValueError, match="extended slice"): + vlarray[::2] = [9] + with pytest.raises(TypeError): + vlarray[1:2] = 3 + with pytest.raises(ValueError): + _ = vlarray[::0] + + +def test_vlarray_copy(): + urlpath = "test_vlarray_copy.b2frame" + copy_path = "test_vlarray_copy_out.b2frame" + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + original = blosc2.VLArray(urlpath=urlpath, mode="w", contiguous=True) + original.extend(VALUES) + original.insert(1, {"copy": True}) + + copied = original.copy( + urlpath=copy_path, contiguous=False, cparams={"codec": blosc2.Codec.LZ4, "clevel": 5} + ) + assert list(copied) == list(original) + assert copied.urlpath == copy_path + assert copied.schunk.contiguous is False + assert copied.cparams.codec == blosc2.Codec.LZ4 + assert copied.cparams.clevel == 5 + + inmem = original.copy() + assert list(inmem) == list(original) + assert inmem.urlpath is None + + with pytest.raises(ValueError, match="meta should not be passed to copy"): + original.copy(meta={}) + + blosc2.remove_urlpath(urlpath) + blosc2.remove_urlpath(copy_path) + + +def test_vlarray_empty_list_roundtrip(): + values = [[], {"a": []}, [[], ["nested"]], None, ("tuple", []), {"rows": [[], []]}] + vlarray = blosc2.VLArray() + vlarray.extend(values) + assert list(vlarray) == values + + +def test_vlarray_empty_tuple_roundtrip(): + values = [(), {"a": ()}, [(), ("nested",)], None, ("tuple", ()), {"rows": [[], ()]}] + vlarray = blosc2.VLArray() + vlarray.extend(values) + assert list(vlarray) == values + + +def test_vlarray_insert_delete_errors(): + vlarray = blosc2.VLArray() + vlarray.append("value") + + with pytest.raises(TypeError): + vlarray.insert("0", "bad") + with pytest.raises(IndexError): + vlarray.delete(3) + with pytest.raises(IndexError): + blosc2.VLArray().pop() + with pytest.raises(NotImplementedError): + vlarray.pop(slice(0, 1)) diff --git a/tests/test_vlmeta.py b/tests/test_vlmeta.py index 8269f43c..ec5f0784 100644 --- a/tests/test_vlmeta.py +++ b/tests/test_vlmeta.py @@ -118,3 +118,12 @@ def clear(schunk): schunk.vlmeta.clear() assert schunk.vlmeta.__len__() == 0 + + +def test_vlmeta_empty_list_roundtrip(): + schunk = blosc2.SChunk() + schunk.vlmeta["empty"] = [] + schunk.vlmeta["nested"] = {"rows": [[], ["x"]]} + + assert schunk.vlmeta["empty"] == [] + assert schunk.vlmeta["nested"] == {"rows": [[], ["x"]]}