Skip to content

Commit 5c933ec

Browse files
author
Luke Shaw
committed
Merge branch 'main' into addStack
2 parents 89c0882 + 51e801f commit 5c933ec

15 files changed

Lines changed: 77 additions & 67 deletions

.github/workflows/cibuildwheels.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
arch: amd64
8484

8585
- name: Build wheels
86-
uses: pypa/cibuildwheel@v2.23
86+
uses: pypa/cibuildwheel@v3.0
8787

8888
- name: Make sdist
8989
if: ${{ matrix.os == 'ubuntu-latest' }}

RELEASING.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ Checking packaging
110110

111111
[upload e.g.:] blosc2-3.2.0-cp312-cp312-pyodide_2024_0_wasm32.whl
112112

113-
113+
The wheels may be downloaded by going to "Actions->Python wheels for WASM" and selecting the completed workflow run for the version you are releasing.
114+
Then, go to the "Artifacts" dropdown and download the WASM wheel file.
114115
Announcing
115116
----------
116117

bench/ndarray/concatenate.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from matplotlib.ticker import ScalarFormatter
1515

1616

17-
def run_benchmark(num_arrays=10, size=500, aligned_chunks=False, axis=0, codec=blosc2.Codec.ZSTD):
17+
def run_benchmark(num_arrays=10, size=500, aligned_chunks=False, axis=0,
18+
dtype=np.float64, datadist="linspace", codec=blosc2.Codec.ZSTD):
1819
"""
1920
Benchmark blosc2.concatenate performance with different chunk alignments.
2021
@@ -23,6 +24,9 @@ def run_benchmark(num_arrays=10, size=500, aligned_chunks=False, axis=0, codec=b
2324
- size: Base size for array dimensions
2425
- aligned_chunks: Whether to use aligned chunk shapes
2526
- axis: Axis along which to concatenate (0 or 1)
27+
- dtype: Data type for the arrays (default is np.float64)
28+
- datadist: Distribution of data in arrays (default is "linspace")
29+
- codec: Codec to use for compression (default is blosc2.Codec.ZSTD)
2630
2731
Returns:
2832
- duration: Time taken in seconds
@@ -39,20 +43,28 @@ def run_benchmark(num_arrays=10, size=500, aligned_chunks=False, axis=0, codec=b
3943
raise ValueError("Only axis 0 and 1 are supported")
4044

4145
# Create appropriate chunk shapes
46+
chunks, blocks = blosc2.compute_chunks_blocks(shapes[0], dtype=dtype, cparams=blosc2.CParams(codec=codec))
4247
if aligned_chunks:
4348
# Aligned chunks: divisors of the shape dimensions
44-
chunk_shapes = [(shape[0] // 4, shape[1] // 4) for shape in shapes]
49+
chunk_shapes = [(chunks[0], chunks[1]) for shape in shapes]
4550
else:
4651
# Unaligned chunks: not divisors of shape dimensions
47-
chunk_shapes = [(shape[0] // 4 + 1, shape[1] // 4 - 1) for shape in shapes]
52+
chunk_shapes = [(chunks[0] + 1, chunks[1] - 1) for shape in shapes]
4853

4954
# Create arrays
5055
arrays = []
5156
for i, (shape, chunk_shape) in enumerate(zip(shapes, chunk_shapes)):
52-
arr = blosc2.arange(
53-
i * np.prod(shape), (i + 1) * np.prod(shape), 1, dtype="i4", shape=shape, chunks=chunk_shape,
54-
cparams=blosc2.CParams(codec=codec)
55-
)
57+
if datadist == "linspace":
58+
# Create arrays with linearly spaced values
59+
arr = blosc2.linspace(i, i + 1, num=np.prod(shape),
60+
dtype=dtype, shape=shape, chunks=chunk_shape,
61+
cparams=blosc2.CParams(codec=codec))
62+
else:
63+
# Default to arange for simplicity
64+
arr = blosc2.arange(
65+
i * np.prod(shape), (i + 1) * np.prod(shape), 1, dtype=dtype, shape=shape, chunks=chunk_shape,
66+
cparams=blosc2.CParams(codec=codec)
67+
)
5668
arrays.append(arr)
5769

5870
# Calculate total data size in GB (4 bytes per int32)
@@ -67,14 +79,16 @@ def run_benchmark(num_arrays=10, size=500, aligned_chunks=False, axis=0, codec=b
6779
return duration, result.shape, data_size_gb
6880

6981

70-
def run_numpy_benchmark(num_arrays=10, size=500, axis=0):
82+
def run_numpy_benchmark(num_arrays=10, size=500, axis=0, dtype=np.float64, datadist="linspace"):
7183
"""
7284
Benchmark numpy.concatenate performance for comparison.
7385
7486
Parameters:
7587
- num_arrays: Number of arrays to concatenate
7688
- size: Base size for array dimensions
7789
- axis: Axis along which to concatenate (0 or 1)
90+
- dtype: Data type for the arrays (default is np.float64)
91+
- datadist: Distribution of data in arrays (default is "linspace")
7892
7993
Returns:
8094
- duration: Time taken in seconds
@@ -93,12 +107,11 @@ def run_numpy_benchmark(num_arrays=10, size=500, axis=0):
93107
# Create arrays
94108
numpy_arrays = []
95109
for i, shape in enumerate(shapes):
96-
arr = np.arange(
97-
i * np.prod(shape),
98-
(i + 1) * np.prod(shape),
99-
1,
100-
dtype="i4"
101-
).reshape(shape)
110+
if datadist == "linspace":
111+
# Create arrays with linearly spaced values
112+
arr = np.linspace(i, i + 1, num=np.prod(shape), dtype=dtype).reshape(shape)
113+
else:
114+
arr = np.arange(i * np.prod(shape), (i + 1) * np.prod(shape), 1, dtype=dtype).reshape(shape)
102115
numpy_arrays.append(arr)
103116

104117
# Calculate total data size in GB (4 bytes per int32)
@@ -114,7 +127,8 @@ def run_numpy_benchmark(num_arrays=10, size=500, axis=0):
114127

115128

116129
def create_combined_plot(num_arrays, sizes, numpy_speeds_axis0, unaligned_speeds_axis0, aligned_speeds_axis0,
117-
numpy_speeds_axis1, unaligned_speeds_axis1, aligned_speeds_axis1, output_dir="plots"):
130+
numpy_speeds_axis1, unaligned_speeds_axis1, aligned_speeds_axis1, output_dir="plots",
131+
datadist="linspace", codec_str="LZ4"):
118132
"""
119133
Create a figure with two side-by-side bar plots comparing the performance for both axes.
120134
@@ -148,7 +162,7 @@ def create_combined_plot(num_arrays, sizes, numpy_speeds_axis0, unaligned_speeds
148162
# Add labels and titles
149163
for ax, axis in [(ax0, 0), (ax1, 1)]:
150164
ax.set_xlabel('Array Size (N for NxN array)', fontsize=12)
151-
ax.set_title(f'Concatenation Performance for {num_arrays} arrays (axis={axis})', fontsize=14)
165+
ax.set_title(f'Concatenation Performance for {num_arrays} arrays (axis={axis}) [{datadist}, {codec_str}]', fontsize=14)
152166
ax.set_xticks(x)
153167
ax.set_xticklabels(x_labels)
154168
ax.grid(True, axis='y', linestyle='--', alpha=0.7)
@@ -186,22 +200,25 @@ def autolabel(rects, ax):
186200

187201
# Save the plot
188202
plt.tight_layout()
189-
plt.savefig(os.path.join(output_dir, 'concatenate_benchmark_combined.png'), dpi=300)
203+
plt.savefig(os.path.join(output_dir, 'concatenate_benchmark_combined.png'), dpi=100)
190204
plt.show()
191205
plt.close()
192206

193207
print(f"Combined plot saved to {os.path.join(output_dir, 'concatenate_benchmark_combined.png')}")
194208

195209

196210
def main():
197-
codec = blosc2.Codec.BLOSCLZ
211+
# Parameters
212+
sizes = [500, 1000, 2000, 4000, 10000] #, 20000] # Sizes of arrays to test
213+
num_arrays = 10
214+
dtype = np.float64 # Data type for arrays
215+
datadist = "linspace" # Distribution of data in arrays
216+
codec = blosc2.Codec.LZ4
217+
codec_str = str(codec).split('.')[-1]
198218
print(f"{'=' * 70}")
199-
print(f"Blosc2 vs NumPy concatenation benchmark {codec=}")
219+
print(f"Blosc2 vs NumPy concatenation benchmark with {codec_str} codec")
200220
print(f"{'=' * 70}")
201221

202-
# Parameters
203-
sizes = [500, 1000, 2000, 4000] #, 10000] # must be divisible by 4 for aligned chunks
204-
num_arrays = 10
205222

206223
# Lists to store results for both axes
207224
numpy_speeds_axis0 = []
@@ -212,16 +229,18 @@ def main():
212229
aligned_speeds_axis1 = []
213230

214231
for axis in [0, 1]:
215-
print(f"\nConcatenating {num_arrays} arrays along axis {axis}")
232+
print(f"\nConcatenating {num_arrays} arrays along axis {axis} with data distribution '{datadist}' ")
216233
print(f"{'Size':<8} {'NumPy (GB/s)':<14} {'Unaligned (GB/s)':<18} "
217234
f"{'Aligned (GB/s)':<16} {'Alig vs Unalig':<16} {'Alig vs NumPy':<16}")
218235
print(f"{'-' * 90}")
219236

220237
for size in sizes:
221238
# Run the benchmarks
222-
numpy_time, numpy_shape, data_size_gb = run_numpy_benchmark(num_arrays, size, axis=axis)
223-
unaligned_time, shape1, _ = run_benchmark(num_arrays, size, aligned_chunks=False, axis=axis, codec=codec)
224-
aligned_time, shape2, _ = run_benchmark(num_arrays, size, aligned_chunks=True, axis=axis, codec=codec)
239+
numpy_time, numpy_shape, data_size_gb = run_numpy_benchmark(num_arrays, size, axis=axis, dtype=dtype)
240+
unaligned_time, shape1, _ = run_benchmark(num_arrays, size, aligned_chunks=False, axis=axis,
241+
dtype=dtype, datadist=datadist, codec=codec)
242+
aligned_time, shape2, _ = run_benchmark(num_arrays, size, aligned_chunks=True, axis=axis,
243+
dtype=dtype, datadist=datadist, codec=codec)
225244

226245
# Calculate throughputs in GB/s
227246
numpy_speed = data_size_gb / numpy_time if numpy_time > 0 else float("inf")
@@ -266,7 +285,8 @@ def main():
266285
num_arrays,
267286
sizes,
268287
numpy_speeds_axis0, unaligned_speeds_axis0, aligned_speeds_axis0,
269-
numpy_speeds_axis1, unaligned_speeds_axis1, aligned_speeds_axis1
288+
numpy_speeds_axis1, unaligned_speeds_axis1, aligned_speeds_axis1,
289+
datadist=datadist, output_dir="plots", codec_str=codec_str,
270290
)
271291

272292

-243 KB
Binary file not shown.
64.3 KB
Loading
63.4 KB
Loading
63.6 KB
Loading

src/blosc2/core.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,9 +1092,7 @@ def detect_number_of_cores() -> int:
10921092
# Dictionaries for the maps between compressor names and libs
10931093
codecs = compressor_list(plugins=True)
10941094
# Map for compression libraries and versions
1095-
clib_versions = {}
1096-
for codec in compressor_list(plugins=False):
1097-
clib_versions[codec.name] = clib_info(codec)[1].decode("utf-8")
1095+
clib_versions = {codec.name: clib_info(codec)[1].decode("utf-8") for codec in compressor_list(plugins=False)}
10981096

10991097

11001098
def os_release_pretty_name():
@@ -1422,15 +1420,14 @@ def compute_partition(nitems, maxshape, minpart=None):
14221420
if rsize <= max_items:
14231421
# rsize = rsize if size % rsize == 0 else nearest_divisor(size, rsize)
14241422
rsize = rsize if size % rsize == 0 else blosc2_ext.nearest_divisor(size, rsize)
1425-
partition[-(i + 1)] = rsize
14261423
else:
14271424
rsize = max(max_items, minsize)
14281425
# new_rsize = rsize if size % rsize == 0 else nearest_divisor(size, rsize, strict=True)
14291426
new_rsize = rsize if size % rsize == 0 else blosc2_ext.nearest_divisor(size, rsize, strict=True)
14301427
# If the new rsize is not too far from the original rsize, use it
14311428
if rsize // 2 < new_rsize < rsize * 2:
14321429
rsize = new_rsize
1433-
partition[-(i + 1)] = rsize
1430+
partition[-(i + 1)] = rsize
14341431
max_items //= rsize
14351432

14361433
return partition

src/blosc2/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def wrapper(child_func):
3232
# Next parameter starts, stop copying lines
3333
break
3434
matching_lines.append(line)
35-
assert len(matching_lines) > 0, (
35+
assert matching_lines, (
3636
f"Could not extract the parameter {parameter} from the docstring of {parent_func.__name__}"
3737
)
3838

src/blosc2/lazyexpr.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ def conserve_functions( # noqa: C901
657657
expression: str,
658658
operands_old: dict[str, blosc2.NDArray | blosc2.LazyExpr],
659659
operands_new: dict[str, blosc2.NDArray | blosc2.LazyExpr],
660-
) -> tuple(str, dict[str, blosc2.NDArray]):
660+
) -> tuple[str, dict[str, blosc2.NDArray]]:
661661
"""
662662
Given an expression in string form, return its operands.
663663
@@ -738,8 +738,6 @@ def visit_Name(self, node):
738738
node.id = newexpr.replace(";", "")
739739
else:
740740
node.id = self.update_func(localop)
741-
else:
742-
pass
743741
self.generic_visit(node)
744742

745743
def visit_Call(self, node):
@@ -784,7 +782,7 @@ def convert_to_slice(expression):
784782
slicer = str(slicer)
785783
# use slice so that lazyexpr uses blosc arrays internally
786784
# (and doesn't decompress according to getitem syntax)
787-
new_expr += ".slice(" + slicer + ")"
785+
new_expr += f".slice({slicer})"
788786
skip_to_char = i + k + 1
789787
else:
790788
new_expr += expr_i
@@ -833,11 +831,11 @@ def extract_numpy_scalars(expr: str):
833831

834832
def validate_inputs(inputs: dict, out=None, reduce=False) -> tuple: # noqa: C901
835833
"""Validate the inputs for the expression."""
836-
if len(inputs) == 0:
834+
if not inputs:
837835
if out is None:
838836
raise ValueError(
839837
"You really want to pass at least one input or one output for building a LazyArray."
840-
" Maybe you want blosc2.empty() instead?"
838+
" Maybe you want blosc2.empty() instead?"
841839
)
842840
if isinstance(out, blosc2.NDArray):
843841
return out.shape, out.chunks, out.blocks, True
@@ -854,7 +852,7 @@ def validate_inputs(inputs: dict, out=None, reduce=False) -> tuple: # noqa: C90
854852

855853
# More checks specific of NDArray inputs
856854
NDinputs = [input for input in inputs if hasattr(input, "chunks")]
857-
if len(NDinputs) == 0:
855+
if not NDinputs:
858856
# All inputs are NumPy arrays, so we cannot take the fast path
859857
if inputs and hasattr(inputs[0], "shape"):
860858
shape = inputs[0].shape
@@ -895,7 +893,7 @@ def is_full_slice(item):
895893
elif isinstance(item, int | bool):
896894
return False
897895
else:
898-
return item == slice(None, None, None) or item == Ellipsis
896+
return item in (slice(None, None, None), Ellipsis)
899897

900898

901899
def do_slices_intersect(slice1: list | tuple, slice2: list | tuple) -> bool:
@@ -1925,7 +1923,7 @@ def fuse_expressions(expr, new_base, dup_op):
19251923
if i < skip_to_char:
19261924
continue
19271925
if expr_i == "o":
1928-
if i > 0 and (expr[i - 1] != " " and expr[i - 1] != "("):
1926+
if i > 0 and expr[i - 1] not in {" ", "("}:
19291927
# Not a variable
19301928
new_expr += expr_i
19311929
continue
@@ -2960,18 +2958,18 @@ def info(self):
29602958

29612959
@property
29622960
def info_items(self):
2963-
items = []
2964-
items += [("type", f"{self.__class__.__name__}")]
29652961
inputs = {}
29662962
for key, value in self.inputs_dict.items():
29672963
if isinstance(value, np.ndarray | blosc2.NDArray | blosc2.C2Array):
29682964
inputs[key] = f"<{value.__class__.__name__}> {value.shape} {value.dtype}"
29692965
else:
29702966
inputs[key] = str(value)
2971-
items += [("inputs", inputs)]
2972-
items += [("shape", self.shape)]
2973-
items += [("dtype", self.dtype)]
2974-
return items
2967+
return [
2968+
("type", f"{self.__class__.__name__}"),
2969+
("inputs", inputs),
2970+
("shape", self.shape),
2971+
("dtype", self.dtype),
2972+
]
29752973

29762974
# TODO: indices and sort are repeated in LazyExpr; refactor
29772975
def indices(self, order: str | list[str] | None = None) -> blosc2.LazyArray:

0 commit comments

Comments
 (0)