Skip to content

Commit 03d98bf

Browse files
committed
To avoid cratio differences between CPUs, better use fixed budgets instead of CPU cache sizes
1 parent 24fe499 commit 03d98bf

2 files changed

Lines changed: 25 additions & 16 deletions

File tree

src/blosc2/batch_array.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,13 @@ class BatchArray:
177177
Maximum number of items stored in each internal variable-length block.
178178
The last block in a batch may contain fewer items than this cap. If not
179179
provided, a value is inferred from the first batch using the serialized
180-
item sizes and the compression level. For ``clevel`` 1 through 5, the
181-
heuristic targets the L1 data-cache size for faster scans and item
182-
lookups. For ``clevel`` 6 through 8, it targets the L2 cache size for
183-
larger blocks and better compression. At ``clevel`` 9, the whole batch
184-
is kept as one block. Smaller blocks generally improve random access
185-
and cache behavior, while larger blocks generally improve compression
186-
ratio.
180+
item sizes and the compression level. The heuristic uses fixed byte
181+
budgets so that the layout (and hence the compression ratio) does not
182+
depend on the CPU it was created on: 1 MiB for ``clevel`` 1 through 3,
183+
8 MiB for ``clevel`` 4 through 6, and 16 MiB for ``clevel`` 7 and 8.
184+
At ``clevel`` 9, the whole batch is kept as one block. Smaller blocks
185+
generally improve random access, while larger blocks generally improve
186+
compression ratio.
187187
serializer : {"msgpack", "arrow"}, optional
188188
Serializer used for batch payloads. ``"msgpack"`` is the default and is
189189
the general-purpose choice for Python items, including nested Blosc2
@@ -609,14 +609,19 @@ def _guess_blocksize(self, payload_sizes: list[int]) -> int:
609609
# too small for codecs like Zstd to exploit cross-row redundancy. Use larger
610610
# cache-budget tiers as clevel increases, while avoiding full L2 blocks at the
611611
# default clevel to keep random access reasonably granular.
612+
# UPDATE: to avoid cratio differences between CPUs, better use fixed budgets instead
613+
# of CPU cache sizes.
612614
if clevel == 9:
613615
return len(payload_sizes)
614616
if 0 < clevel <= 3:
615-
budget = blosc2.cpu_info.get("l1_data_cache_size")
617+
# budget = blosc2.cpu_info.get("l1_data_cache_size")
618+
budget = 2**20 # 1 MB
616619
elif 3 < clevel <= 6:
617-
budget = blosc2.cpu_info.get("l2_cache_size") // 2
620+
# budget = blosc2.cpu_info.get("l2_cache_size") // 2
621+
budget = 2**23 # 8 MB
618622
elif 6 < clevel < 9:
619-
budget = blosc2.cpu_info.get("l2_cache_size")
623+
# budget = blosc2.cpu_info.get("l2_cache_size")
624+
budget = 2**24 # 16 MB
620625
else:
621626
return len(payload_sizes)
622627
if not isinstance(budget, int) or budget <= 0:

tests/test_batch_array.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,25 +380,29 @@ def test_batcharray_respects_explicit_use_dict_and_non_zstd():
380380
assert barray.cparams.use_dict is False
381381

382382

383-
def test_batcharray_guess_items_per_block_uses_l1_for_low_clevel(monkeypatch):
383+
def test_batcharray_guess_items_per_block_uses_1mib_budget_for_low_clevel(monkeypatch):
384+
# Budgets are fixed; detected cache sizes must not influence the layout.
384385
monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 100)
385386
monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 1000)
386387
barray = blosc2.BatchArray(cparams={"clevel": 3})
387-
assert barray._guess_blocksize([30, 30, 30, 30]) == 3
388+
# 1 MiB budget: three 300 KiB payloads fit, a fourth would exceed it
389+
assert barray._guess_blocksize([300 * 1024] * 4) == 3
388390

389391

390-
def test_batcharray_guess_items_per_block_uses_half_l2_for_default_clevel(monkeypatch):
392+
def test_batcharray_guess_items_per_block_uses_8mib_budget_for_default_clevel(monkeypatch):
391393
monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 100)
392394
monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 150)
393395
barray = blosc2.BatchArray(cparams={"clevel": 5})
394-
assert barray._guess_blocksize([60, 60, 60, 60]) == 1
396+
# 8 MiB budget: a single 5 MiB payload fits, a second would exceed it
397+
assert barray._guess_blocksize([5 * 2**20] * 4) == 1
395398

396399

397-
def test_batcharray_guess_items_per_block_uses_l2_for_high_clevel(monkeypatch):
400+
def test_batcharray_guess_items_per_block_uses_16mib_budget_for_high_clevel(monkeypatch):
398401
monkeypatch.setitem(blosc2.cpu_info, "l1_data_cache_size", 100)
399402
monkeypatch.setitem(blosc2.cpu_info, "l2_cache_size", 150)
400403
barray = blosc2.BatchArray(cparams={"clevel": 7})
401-
assert barray._guess_blocksize([60, 60, 60, 60]) == 2
404+
# 16 MiB budget: two 6 MiB payloads fit, a third would exceed it
405+
assert barray._guess_blocksize([6 * 2**20] * 4) == 2
402406

403407

404408
def test_batcharray_guess_items_per_block_uses_full_batch_for_clevel_9(monkeypatch):

0 commit comments

Comments
 (0)