Skip to content

Commit e3d3748

Browse files
authored
Merge branch 'main' into openzl_plugin
2 parents 0a8f9b1 + 39ccb39 commit e3d3748

25 files changed

Lines changed: 1243 additions & 132 deletions

.github/workflows/build.yml

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
build_wheels:
1313
name: Build and test on ${{ matrix.os }}${{ matrix.numpy-version && format(' (numpy {0})', matrix.numpy-version) || matrix.python-version && format(' (python {0})', matrix.python-version) || '' }}
1414
runs-on: ${{ matrix.os }}
15+
env:
16+
CMAKE_GENERATOR: Ninja
1517
strategy:
1618
matrix:
1719
os: [ubuntu-latest, windows-latest, macos-latest]
@@ -33,15 +35,65 @@ jobs:
3335
with:
3436
python-version: ${{ matrix.python-version }}
3537

38+
- name: Install sccache (Windows)
39+
if: runner.os == 'Windows'
40+
run: choco install sccache --yes
41+
42+
- name: Cache sccache (Windows)
43+
if: runner.os == 'Windows'
44+
uses: actions/cache@v4
45+
with:
46+
path: C:\Users\runneradmin\AppData\Local\sccache
47+
key: sccache-${{ runner.os }}-${{ github.sha }}
48+
restore-keys: |
49+
sccache-${{ runner.os }}-
50+
51+
- name: Cache pip (Windows)
52+
if: runner.os == 'Windows'
53+
uses: actions/cache@v4
54+
with:
55+
path: C:\Users\runneradmin\AppData\Local\pip\Cache
56+
key: pip-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}
57+
restore-keys: |
58+
pip-${{ runner.os }}-
59+
3660
- name: Install Ninja
3761
uses: seanmiddleditch/gha-setup-ninja@master
3862

63+
- name: Add LLVM to PATH (Windows)
64+
if: runner.os == 'Windows'
65+
run: echo "C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_PATH
66+
3967
- name: Install specific numpy version
4068
if: matrix.numpy-version
4169
run: pip install "numpy==${{ matrix.numpy-version }}.*"
4270

43-
- name: Build
71+
- name: Build (Windows)
72+
if: runner.os == 'Windows'
73+
id: build_windows
74+
run: pip install -e .[test]
75+
env:
76+
CMAKE_C_COMPILER_LAUNCHER: sccache
77+
CMAKE_CXX_COMPILER_LAUNCHER: sccache
78+
SCCACHE_DIR: C:\Users\runneradmin\AppData\Local\sccache
79+
CC: clang-cl
80+
CXX: clang-cl
81+
CMAKE_BUILD_PARALLEL_LEVEL: 8
82+
SKBUILD_PARALLEL_LEVEL: 8
83+
84+
- name: Build (non-Windows)
85+
if: runner.os != 'Windows'
86+
id: build_non_windows
4487
run: pip install -e .[test]
4588

46-
- name: Test
89+
- name: Test (Windows)
90+
if: runner.os == 'Windows'
91+
run: python -m pytest -m "not heavy and (network or not network)"
92+
# env:
93+
# BLOSC_NTHREADS: "1"
94+
# NUMEXPR_NUM_THREADS: "1"
95+
# OMP_NUM_THREADS: "1"
96+
97+
- name: Test (non-Windows)
98+
if: runner.os != 'Windows'
4799
run: python -m pytest -m "not heavy and (network or not network)"

.github/workflows/cibuildwheels.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ env:
1717
# Skip PyPy wheels for now (numexpr needs some adjustments first)
1818
# musllinux takes too long to build, and it's not worth it for now
1919
CIBW_SKIP: "pp* *musllinux* *-win32"
20+
# Use explicit generator/compiler env vars; CMAKE_ARGS with spaces is not split on Windows.
21+
CIBW_ENVIRONMENT_WINDOWS: >-
22+
CMAKE_GENERATOR=Ninja
23+
CC=clang-cl
24+
CXX=clang-cl
2025
2126
jobs:
2227

@@ -77,6 +82,10 @@ jobs:
7782
id: ninja
7883
uses: turtlesec-no/get-ninja@main
7984

85+
- name: Add LLVM to PATH (Windows)
86+
if: ${{ matrix.os == 'windows-latest' }}
87+
run: echo "C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_PATH
88+
8089
- name: Install MSVC amd64
8190
uses: ilammy/msvc-dev-cmd@v1
8291
with:

.github/workflows/wasm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
env:
2222
CIBW_BUILD: ${{ matrix.cibw_build }}
2323
CMAKE_ARGS: "-DWITH_OPTIM=OFF"
24+
DEACTIVATE_OPENZL: "1"
2425
CIBW_TEST_COMMAND: "pytest {project}/tests/ndarray/test_reductions.py"
2526
strategy:
2627
matrix:

CMakeLists.txt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
cmake_minimum_required(VERSION 3.15.0)
2+
3+
if(WIN32)
4+
cmake_policy(SET CMP0091 NEW)
5+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL" CACHE STRING "" FORCE)
6+
endif()
7+
8+
9+
if(WIN32 AND CMAKE_GENERATOR MATCHES "Visual Studio")
10+
if(NOT DEFINED CMAKE_GENERATOR_TOOLSET)
11+
set(CMAKE_GENERATOR_TOOLSET "ClangCL" CACHE STRING "Use ClangCL toolset for C99/C11 support on Windows." FORCE)
12+
endif()
13+
endif()
14+
215
project(python-blosc2)
16+
17+
if(WIN32 AND NOT CMAKE_C_COMPILER_ID STREQUAL "Clang")
18+
message(FATAL_ERROR "Windows builds require clang-cl. Set CC/CXX to clang-cl or configure CMake with -T ClangCL.")
19+
endif()
320
# Specifying Python version below is tricky, but if you don't specify the minimum version here,
421
# it would not consider python3 when looking for the executable. This is problematic since Fedora
522
# does not include a python symbolic link to python3.
@@ -23,11 +40,55 @@ add_custom_command(
2340
"${CMAKE_CURRENT_SOURCE_DIR}/src/blosc2/blosc2_ext.pyx" --output-file blosc2_ext.c
2441
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/blosc2/blosc2_ext.pyx"
2542
VERBATIM)
43+
2644
# ...and add it to the target
2745
Python_add_library(blosc2_ext MODULE blosc2_ext.c WITH_SOABI)
46+
2847
# We need to link against NumPy
2948
target_link_libraries(blosc2_ext PRIVATE Python::NumPy)
3049

50+
# Fetch and build miniexpr library
51+
include(FetchContent)
52+
53+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
54+
set(MINIEXPR_BUILD_SHARED OFF CACHE BOOL "Build miniexpr shared library" FORCE)
55+
set(MINIEXPR_BUILD_TESTS OFF CACHE BOOL "Build miniexpr tests" FORCE)
56+
set(MINIEXPR_BUILD_EXAMPLES OFF CACHE BOOL "Build miniexpr examples" FORCE)
57+
set(MINIEXPR_BUILD_BENCH OFF CACHE BOOL "Build miniexpr benchmarks" FORCE)
58+
59+
FetchContent_Declare(miniexpr
60+
GIT_REPOSITORY https://github.com/Blosc/miniexpr.git
61+
GIT_TAG 979573da618e0443c3984bad8db3ed5d9ce72f75 # latest commit in main
62+
# In case you want to use a local copy of miniexpr for development, uncomment the line below
63+
# SOURCE_DIR "/Users/faltet/blosc/miniexpr"
64+
)
65+
FetchContent_MakeAvailable(miniexpr)
66+
67+
# Link against miniexpr static library
68+
target_link_libraries(blosc2_ext PRIVATE miniexpr_static)
69+
70+
target_compile_features(blosc2_ext PRIVATE c_std_11)
71+
if(WIN32 AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
72+
execute_process(
73+
COMMAND "${CMAKE_C_COMPILER}" -print-resource-dir
74+
OUTPUT_VARIABLE _clang_resource_dir
75+
OUTPUT_STRIP_TRAILING_WHITESPACE
76+
ERROR_QUIET
77+
)
78+
if(_clang_resource_dir)
79+
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
80+
set(_clang_builtins "${_clang_resource_dir}/lib/windows/clang_rt.builtins-x86_64.lib")
81+
else()
82+
set(_clang_builtins "${_clang_resource_dir}/lib/windows/clang_rt.builtins-i386.lib")
83+
endif()
84+
if(EXISTS "${_clang_builtins}")
85+
target_link_libraries(blosc2_ext PRIVATE "${_clang_builtins}")
86+
endif()
87+
unset(_clang_builtins)
88+
endif()
89+
unset(_clang_resource_dir)
90+
endif()
91+
3192
if(DEFINED ENV{USE_SYSTEM_BLOSC2})
3293
set(USE_SYSTEM_BLOSC2 ON)
3394
endif()
@@ -44,6 +105,9 @@ else()
44105
set(BUILD_EXAMPLES OFF CACHE BOOL "Build C-Blosc2 examples")
45106
set(BUILD_BENCHMARKS OFF CACHE BOOL "Build C-Blosc2 benchmarks")
46107
set(BUILD_FUZZERS OFF CACHE BOOL "Build C-Blosc2 fuzzers")
108+
if(DEFINED ENV{DEACTIVATE_OPENZL})
109+
set(DEACTIVATE_OPENZL ON CACHE BOOL "Do not include support for the OpenZL library.")
110+
endif()
47111
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
48112
# we want the binaries of the C-Blosc2 library to go into the wheels
49113
set(BLOSC_INSTALL ON)

README.rst

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ Conda users can install from conda-forge:
5353
5454
conda install -c conda-forge python-blosc2
5555
56+
Windows note
57+
============
58+
59+
When building from source on Windows, clang-cl is required (OpenZL depends on C11 support).
60+
Make sure LLVM is on PATH and use the Ninja generator, for example::
61+
62+
CMAKE_GENERATOR=Ninja
63+
CC=clang-cl
64+
CXX=clang-cl
65+
pip install -e .
66+
5667
Documentation
5768
=============
5869

@@ -64,9 +75,11 @@ You can find examples at:
6475

6576
https://github.com/Blosc/python-blosc2/tree/main/examples
6677

67-
A tutorial from PyData Global 2024 is available at:
78+
A tutorial from PyData Global 2025 is available at:
79+
80+
https://github.com/Blosc/PyData-Global-2025-Tutorial
6881

69-
https://github.com/Blosc/Python-Blosc2-3.0-tutorial
82+
(`Clik here <https://www.youtube.com/watch?v=tUvSI3EpTBQ&list=PLGVZCDnMOq0qmerwB1eITnr5AfYRGm0DF&index=81>`_ to watch the video recording of the tutorial)
7083

7184
It contains Jupyter notebooks explaining the main features of Python-Blosc2.
7285

README_DEVELOPERS.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ You are done!
2222
pip install . # add -e for editable mode
2323
```
2424

25+
On Windows, clang-cl is required (OpenZL depends on C11 support). Make sure LLVM
26+
is on PATH and build with Ninja, for example:
27+
28+
```bash
29+
CMAKE_GENERATOR=Ninja \
30+
CC=clang-cl \
31+
CXX=clang-cl \
32+
pip install -e .
33+
```
34+
2535
There are situations where you may want to build the C-Blosc2 library separately, for example, when debugging issues in the C library. In that case, let's assume you have the C-Blosc2 library installed in `/usr/local`:
2636

2737
```bash
@@ -38,6 +48,31 @@ LD_LIBRARY_PATH=/usr/local/lib pytest
3848

3949
That's it! You can now proceed to the testing section.
4050

51+
### Speeding up local builds (sccache + Ninja)
52+
53+
If you do frequent local rebuilds, sccache can significantly speed up C/C++ rebuilds.
54+
55+
```bash
56+
brew install sccache ninja
57+
```
58+
59+
Then run:
60+
61+
```bash
62+
CMAKE_C_COMPILER_LAUNCHER=sccache \
63+
SKBUILD_BUILD_DIR=build \
64+
pip install -e . --no-build-isolation
65+
```
66+
67+
Using `SKBUILD_BUILD_DIR` keeps a stable build directory between runs, which
68+
improves incremental rebuilds and sccache hit rates.
69+
70+
Check cache stats with:
71+
72+
```bash
73+
sccache --show-stats
74+
```
75+
4176
## Testing
4277

4378
We are using pytest for testing. You can run the tests by executing

bench/ndarray/miniexpr-eval.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from time import time
2+
import blosc2
3+
import numpy as np
4+
import numexpr as ne
5+
6+
N = 10_000
7+
# dtype= np.int32
8+
dtype= np.float32
9+
# dtype= np.float64
10+
cparams = blosc2.CParams(codec=blosc2.Codec.BLOSCLZ, clevel=1)
11+
12+
t0 = time()
13+
# a = blosc2.ones((N, N), dtype=dtype, cparams=cparams)
14+
# a = blosc2.arange(np.prod((N, N)), shape=(N, N), dtype=dtype, cparams=cparams)
15+
a = blosc2.linspace(0., 1., np.prod((N, N)), shape=(N, N), dtype=dtype, cparams=cparams)
16+
print(f"Time to create data: {(time() - t0) * 1000 :.4f} ms")
17+
t0 = time()
18+
b = a.copy()
19+
c = a.copy()
20+
print(f"Time to copy data: {(time() - t0) * 1000 :.4f} ms")
21+
22+
t0 = time()
23+
res = (2 * a**2 - 3 * b + c + 1.2).compute(cparams=cparams)
24+
t = time() - t0
25+
print(f"Time to evaluate: {t * 1000 :.4f} ms", end=" ")
26+
print(f"Speed (GB/s): {(a.nbytes * 4 / 1e9) / t:.2f}")
27+
# print(res.info)
28+
29+
na = a[:]
30+
nb = b[:]
31+
nc = c[:]
32+
33+
t0 = time()
34+
nres = 2 * na**2 - 3 * nb + nc + 1.2
35+
nt = time() - t0
36+
print(f"Time to evaluate with NumPy: {nt * 1000 :.4f} ms", end=" ")
37+
print(f"Speed (GB/s): {(na.nbytes * 4 / 1e9) / nt:.2f}")
38+
print(f"Speedup Blosc2 vs NumPy: {nt / t:.2f}x")
39+
np.testing.assert_allclose(res, nres, rtol=1e-5)
40+
41+
t0 = time()
42+
neres = ne.evaluate("2 * na**2 - 3 * nb + nc + 1.2")
43+
net = time() - t0
44+
print(f"Time to evaluate with NumExpr: {net * 1000 :.4f} ms", end=" ")
45+
print(f"Speed (GB/s): {(na.nbytes * 4 / 1e9) / net:.2f}")
46+
print(f"Speedup Blosc2 vs NumExpr: {net / t:.2f}x")
47+
np.testing.assert_allclose(res, neres, rtol=1e-5)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from time import time
2+
import blosc2
3+
import numpy as np
4+
import numexpr as ne
5+
6+
N = 10_000
7+
dtype= np.float32
8+
cparams = blosc2.CParams(codec=blosc2.Codec.BLOSCLZ, clevel=1)
9+
10+
t0 = time()
11+
#a = blosc2.ones((N, N), dtype=dtype, cparams=cparams)
12+
#a = blosc2.arange(np.prod((N, N)), shape=(N, N), dtype=dtype, cparams=cparams)
13+
a = blosc2.linspace(0., 1., np.prod((N, N)), shape=(N, N), dtype=dtype, cparams=cparams)
14+
#rng = np.random.default_rng(1234)
15+
#a = rng.integers(0, 2, size=(N, N), dtype=dtype)
16+
#a = blosc2.asarray(a, cparams=cparams, urlpath="a.b2nd", mode="w")
17+
print(f"Time to create data: {(time() - t0) * 1000 :.4f} ms")
18+
t0 = time()
19+
b = a.copy()
20+
c = a.copy()
21+
print(f"Time to copy data: {(time() - t0) * 1000 :.4f} ms")
22+
23+
t0 = time()
24+
res = blosc2.sum(2 * a**2 - 3 * b + c + 1.2)
25+
t = time() - t0
26+
print(f"Time to evaluate: {t * 1000 :.4f} ms", end=" ")
27+
print(f"Speed (GB/s): {(a.nbytes * 3 / 1e9) / t:.2f}")
28+
print("Result:", res, "Mean:", res / (N * N))
29+
30+
na = a[:]
31+
nb = b[:]
32+
nc = c[:]
33+
34+
t0 = time()
35+
nres = np.sum(2 * na**2 - 3 * nb + nc + 1.2)
36+
nt = time() - t0
37+
print(f"Time to evaluate with NumPy: {nt * 1000 :.4f} ms", end=" ")
38+
print(f"Speed (GB/s): {(na.nbytes * 3 / 1e9) / nt:.2f}")
39+
print("Result:", nres, "Mean:", nres / (N * N))
40+
print(f"Speedup Blosc2 vs NumPy: {nt / t:.2f}x")
41+
assert np.allclose(res, nres)
42+
43+
t0 = time()
44+
neres = ne.evaluate("sum(2 * na**2 - 3 * nb + nc + 1.2)")
45+
net = time() - t0
46+
print(f"Time to evaluate with NumExpr: {net * 1000 :.4f} ms", end=" ")
47+
print(f"Speed (GB/s): {(na.nbytes * 3 / 1e9) / net:.2f}")
48+
print("Result:", neres, "Mean:", neres / (N * N))
49+
print(f"Speedup Blosc2 vs NumExpr: {net / t:.2f}x")

0 commit comments

Comments
 (0)