Skip to content

Commit a3852b6

Browse files
Merge pull request #614 from Jacc4224/my_ctable3
Start integration with main Python-Blosc2
2 parents 836e989 + 34c2eee commit a3852b6

84 files changed

Lines changed: 14776 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/cibuildwheels.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ env:
2626
jobs:
2727

2828
build_wheels:
29+
if: ${{ github.ref_name != 'ctable3' && github.head_ref != 'ctable3' }}
2930
name: Build wheels on ${{ matrix.os }} for ${{ matrix.arch }}
3031
runs-on: ${{ matrix.runs-on || matrix.os }}
3132
permissions:
@@ -128,10 +129,9 @@ jobs:
128129
129130
130131
upload_pypi:
132+
if: ${{ (github.ref_name != 'ctable3' && github.head_ref != 'ctable3') && startsWith(github.event.ref, 'refs/tags') }}
131133
needs: [ build_wheels]
132134
runs-on: ubuntu-latest
133-
# Only upload wheels when tagging (typically a release)
134-
if: startsWith(github.event.ref, 'refs/tags')
135135
steps:
136136
- uses: actions/download-artifact@v8
137137
with:

.github/workflows/wasm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ env:
1414

1515
jobs:
1616
build_wheels_wasm:
17+
if: ${{ github.ref_name != 'ctable3' && github.head_ref != 'ctable3' }}
1718
name: Build and test wheels for WASM on ${{ matrix.os }} for ${{ matrix.p_ver }}
1819
runs-on: ubuntu-latest
1920
permissions:

bench/ctable/Prueba_iter.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#######################################################################
2+
# Copyright (c) 2019-present, Blosc Development Team <blosc@blosc.org>
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: BSD-3-Clause
6+
#######################################################################
7+
8+
from dataclasses import dataclass
9+
from time import time
10+
11+
import blosc2
12+
from blosc2 import CTable
13+
14+
15+
@dataclass
16+
class Row:
17+
id: int = blosc2.field(blosc2.int64(ge=0))
18+
score: float = blosc2.field(blosc2.float64(ge=0, le=100))
19+
active: bool = blosc2.field(blosc2.bool(), default=True)
20+
21+
22+
N = 1_000 # start small, increase when confident
23+
24+
data = [(i, float(i % 100), i % 2 == 0) for i in range(N)]
25+
tabla = CTable(Row, new_data=data)
26+
27+
print(f"Table created with {len(tabla)} rows\n")
28+
29+
# -------------------------------------------------------------------
30+
# Test 1: iterate without accessing any column (minimum cost)
31+
# -------------------------------------------------------------------
32+
t0 = time()
33+
for _row in tabla:
34+
pass
35+
t1 = time()
36+
print(f"[Test 1] Iter without accessing columns: {(t1 - t0)*1000:.3f} ms")
37+
38+
# -------------------------------------------------------------------
39+
# Test 2: iterate accessing a single column (real_pos cached once)
40+
# -------------------------------------------------------------------
41+
t0 = time()
42+
for row in tabla:
43+
_ = row["id"]
44+
t1 = time()
45+
print(f"[Test 2] Iter accessing 'id': {(t1 - t0)*1000:.3f} ms")
46+
47+
# -------------------------------------------------------------------
48+
# Test 3: iterate accessing all columns (real_pos cached once per row)
49+
# -------------------------------------------------------------------
50+
t0 = time()
51+
for row in tabla:
52+
_ = row["id"]
53+
_ = row["score"]
54+
_ = row["active"]
55+
t1 = time()
56+
print(f"[Test 3] Iter accessing 3 columns: {(t1 - t0)*1000:.3f} ms")
57+
58+
# -------------------------------------------------------------------
59+
# Test 4: correctness — values match expected
60+
# -------------------------------------------------------------------
61+
errors = 0
62+
for row in tabla:
63+
if row["id"] != row._nrow:
64+
errors += 1
65+
if row["score"] != float(row._nrow % 100):
66+
errors += 1
67+
if row["active"] != (row._nrow % 2 == 0):
68+
errors += 1
69+
70+
print(f"\n[Test 4] Correctness errors: {errors} (expected: 0)")
71+
72+
# -------------------------------------------------------------------
73+
# Test 5: with holes (deleted rows)
74+
# -------------------------------------------------------------------
75+
tabla2 = CTable(Row, new_data=data)
76+
tabla2.delete(list(range(0, N, 2))) # delete even rows, keep odd ones
77+
78+
print(f"\nTable with holes: {len(tabla2)} rows (expected: {N // 2})")
79+
80+
t0 = time()
81+
ids = []
82+
for row in tabla2:
83+
ids.append(row["id"])
84+
t1 = time()
85+
86+
expected_ids = [i for i in range(N) if i % 2 != 0]
87+
ok = ids == expected_ids
88+
print(f"[Test 5] Iter with holes ({N//2} rows): {(t1 - t0)*1000:.3f} ms | correctness: {ok}")
89+
90+
# -------------------------------------------------------------------
91+
# Test 6: real_pos is cached correctly (not recomputed)
92+
# -------------------------------------------------------------------
93+
row0 = next(iter(tabla))
94+
assert row0._real_pos is None, "real_pos should be None before first access"
95+
_ = row0["id"]
96+
assert row0._real_pos is not None, "real_pos should be cached after first access"
97+
print(f"\n[Test 6] real_pos caching: OK (real_pos={row0._real_pos})")
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#######################################################################
2+
# Copyright (c) 2019-present, Blosc Development Team <blosc@blosc.org>
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: BSD-3-Clause
6+
#######################################################################
7+
8+
# Benchmark: append() overhead introduced by the new schema pipeline
9+
#
10+
# The new append() path routes every row through:
11+
# _normalize_row_input → validate_row (Pydantic) → _coerce_row_to_storage
12+
#
13+
# This benchmark isolates how much each step costs, and shows the
14+
# total overhead vs the raw NDArray write speed.
15+
16+
from dataclasses import dataclass
17+
from time import perf_counter
18+
19+
import numpy as np
20+
21+
import blosc2
22+
from blosc2.schema_compiler import compile_schema
23+
from blosc2.schema_validation import build_validator_model, validate_row
24+
25+
26+
@dataclass
27+
class Row:
28+
id: int = blosc2.field(blosc2.int64(ge=0))
29+
score: float = blosc2.field(blosc2.float64(ge=0, le=100), default=0.0)
30+
active: bool = blosc2.field(blosc2.bool(), default=True)
31+
32+
33+
N = 5_000
34+
rng = np.random.default_rng(42)
35+
data = [
36+
(int(i), float(rng.uniform(0, 100)), bool(i % 2))
37+
for i in range(N)
38+
]
39+
schema = compile_schema(Row)
40+
# Warm up the Pydantic model cache
41+
build_validator_model(schema)
42+
43+
print(f"append() pipeline cost breakdown | N = {N:,} rows")
44+
print("=" * 60)
45+
46+
# ── 1. Raw NDArray writes (no CTable overhead at all) ────────────────────────
47+
ids = np.zeros(N, dtype=np.int64)
48+
scores = np.zeros(N, dtype=np.float64)
49+
flags = np.zeros(N, dtype=np.bool_)
50+
mask = np.zeros(N, dtype=np.bool_)
51+
52+
t0 = perf_counter()
53+
for i, (id_, score, active) in enumerate(data):
54+
ids[i] = id_
55+
scores[i] = score
56+
flags[i] = active
57+
mask[i] = True
58+
t_raw = perf_counter() - t0
59+
print(f"{'Raw NumPy writes (baseline)':<40} {t_raw:.4f} s")
60+
61+
# ── 2. _normalize_row_input only ─────────────────────────────────────────────
62+
t_obj = blosc2.CTable(Row, expected_size=N, validate=False)
63+
t0 = perf_counter()
64+
for row in data:
65+
_ = t_obj._normalize_row_input(row)
66+
t_normalize = perf_counter() - t0
67+
print(f"{'_normalize_row_input only':<40} {t_normalize:.4f} s ({t_normalize/t_raw:.1f}x baseline)")
68+
69+
# ── 3. Pydantic validate_row only ────────────────────────────────────────────
70+
row_dicts = [t_obj._normalize_row_input(row) for row in data]
71+
t0 = perf_counter()
72+
for rd in row_dicts:
73+
_ = validate_row(schema, rd)
74+
t_validate = perf_counter() - t0
75+
print(f"{'validate_row (Pydantic) only':<40} {t_validate:.4f} s ({t_validate/t_raw:.1f}x baseline)")
76+
77+
# ── 4. _coerce_row_to_storage only ───────────────────────────────────────────
78+
t0 = perf_counter()
79+
for rd in row_dicts:
80+
_ = t_obj._coerce_row_to_storage(rd)
81+
t_coerce = perf_counter() - t0
82+
print(f"{'_coerce_row_to_storage only':<40} {t_coerce:.4f} s ({t_coerce/t_raw:.1f}x baseline)")
83+
84+
# ── 5. Full append(), validate=False (3 runs, take minimum) ─────────────────
85+
RUNS = 3
86+
best_off = float("inf")
87+
for _ in range(RUNS):
88+
t_obj2 = blosc2.CTable(Row, expected_size=N, validate=False)
89+
t0 = perf_counter()
90+
for row in data:
91+
t_obj2.append(row)
92+
best_off = min(best_off, perf_counter() - t0)
93+
t_append_off = best_off
94+
print(f"{'Full append(), validate=False':<40} {t_append_off:.4f} s ({t_append_off/t_raw:.1f}x baseline)")
95+
96+
# ── 6. Full append(), validate=True (3 runs, take minimum) ──────────────────
97+
best_on = float("inf")
98+
for _ in range(RUNS):
99+
t_obj3 = blosc2.CTable(Row, expected_size=N, validate=True)
100+
t0 = perf_counter()
101+
for row in data:
102+
t_obj3.append(row)
103+
best_on = min(best_on, perf_counter() - t0)
104+
t_append_on = best_on
105+
print(f"{'Full append(), validate=True':<40} {t_append_on:.4f} s ({t_append_on/t_raw:.1f}x baseline)")
106+
107+
print()
108+
print("=" * 60)
109+
pydantic_cost = max(t_append_on - t_append_off, 0.0)
110+
print(f"{'Pydantic overhead in append()':<40} {pydantic_cost:.4f} s")
111+
if t_append_on > 0:
112+
print(f"{'Validation fraction of total':<40} {pydantic_cost/t_append_on*100:.1f}%")
113+
print(f"{'Per-row Pydantic cost (isolated)':<40} {(t_validate/N)*1e6:.2f} µs/row")
114+
print()
115+
print(f"Note: append() is dominated by blosc2 I/O ({t_append_off/t_raw:.0f}x raw numpy),")
116+
print(" not by the validation pipeline.")
117+
print(" The main bottleneck is the last_true_pos backward scan per row.")

0 commit comments

Comments
 (0)