99Benchmark for TreeStore hierarchical creation, opening, and listing.
1010
1111Creates a hierarchy of N1 levels, each with N2 NDArray leaves and one
12- CTable (4 cols: bool, int, float, string) with N5 rows. Leaf ``N``
12+ CTable (20 cols: bool, int, float, string plus 16 numeric columns) with
13+ N5 rows. Leaf ``N``
1314receives an *N*-dimensional array (leaf0 is 0‑d, leaf1 is 1‑d, …) with
1415each side ``int(MAX_ELEMS ** (1/N))`` so that no array exceeds MAX_ELEMS
1516elements. Everything is written to ``tree-store.b2z`` and the script
3334
3435# ── Row schema for the CTable ────────────────────────────────────────────
3536
37+ # 4 base columns plus 16 extra numeric ones (v04..v19), wide enough to
38+ # exceed the data panel viewport of b2view.
39+ NCOLS = 20
40+
3641
3742@dataclasses .dataclass
3843class _Row :
3944 a : bool = blosc2 .field (blosc2 .bool (), default = False )
4045 b : int = blosc2 .field (blosc2 .int64 (), default = 0 )
4146 c : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
4247 d : str = ""
48+ v04 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
49+ v05 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
50+ v06 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
51+ v07 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
52+ v08 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
53+ v09 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
54+ v10 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
55+ v11 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
56+ v12 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
57+ v13 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
58+ v14 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
59+ v15 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
60+ v16 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
61+ v17 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
62+ v18 : int = blosc2 .field (blosc2 .int64 (), default = 0 )
63+ v19 : float = blosc2 .field (blosc2 .float64 (), default = 0.0 )
64+
65+
66+ def ctable_values (nrows : int ) -> dict [str , np .ndarray ]:
67+ """Deterministic column values for the CTable; row *i* is predictable.
68+
69+ Tests (e.g. tests/b2view/test_basics.py) rely on these formulas to check
70+ that a given viewport shows the expected values:
71+
72+ - a: i % 2 == 0
73+ - b: i
74+ - c: i * 1.5
75+ - d: "str_%06d" % i
76+ - v{k}, even k: i * k
77+ - v{k}, odd k: linspace(0, k, nrows)[i] == i * k / (nrows - 1)
78+ """
79+ i = np .arange (nrows )
80+ values : dict [str , np .ndarray ] = {
81+ "a" : i % 2 == 0 ,
82+ "b" : i ,
83+ "c" : i * 1.5 ,
84+ "d" : np .char .add ("str_" , np .char .zfill (i .astype ("U6" ), 6 )),
85+ }
86+ for k in range (4 , NCOLS ):
87+ values [f"v{ k :02d} " ] = i * k if k % 2 == 0 else np .linspace (0 , k , num = nrows )
88+ return values
4389
4490
4591# ── Helpers ──────────────────────────────────────────────────────────────
@@ -87,9 +133,16 @@ def create_store(
87133 max_elems : int ,
88134 nrows : int ,
89135 no_vlmeta : bool = False ,
136+ output : str = OUTPUT_FILE ,
137+ verbose : bool = True ,
90138) -> tuple [float , int ]:
91139 """Create the TreeStore; return (wall_clock, total_elements_written)."""
92- _clean (OUTPUT_FILE )
140+
141+ def log (* args , ** kwargs ):
142+ if verbose :
143+ print (* args , ** kwargs )
144+
145+ _clean (output )
93146
94147 # Pre-build one array per unique dimensionality (leaf ``i`` → *i*‑d).
95148 leaf_arrays_np : dict [int , np .ndarray ] = {}
@@ -109,25 +162,30 @@ def create_store(
109162 total_elements = sum (leaf_arrays_np [ndim ].size for ndim in range (nleaves )) * nlevels
110163
111164 # Pre-populate a single CTable that we will copy for every level.
165+ # Columns are filled from vectorized, predictable sequences (arange /
166+ # linspace flavored) so they are fast to build and compress very well.
112167 tmpl_table = blosc2 .CTable (_Row , expected_size = nrows , validate = False )
113- rows = [(i % 2 == 0 , i , float (i ) * 1.5 , f"str_{ i :06d} " ) for i in range (nrows )]
114- tmpl_table .extend (rows , validate = False )
168+ cols = ctable_values (nrows )
169+ struct = np .empty (nrows , dtype = [(name , vals .dtype ) for name , vals in cols .items ()])
170+ for name , vals in cols .items ():
171+ struct [name ] = vals
172+ tmpl_table .extend (struct , validate = False )
115173
116- print (
174+ log (
117175 f"\n Creating TreeStore with { nlevels } level(s), "
118176 f"{ nleaves } leave(s) each, { nrows } CTable row(s) per level..."
119177 )
120- print (f" Max elements per leaf: { max_elems :,} " )
178+ log (f" Max elements per leaf: { max_elems :,} " )
121179 for ndim in range (min (nleaves , 10 )):
122180 shape = _leaf_shape (ndim , max_elems )
123181 nelem = int (np .prod (shape )) if shape else 1
124- print (f" leaf{ ndim } : shape={ shape } , elements={ nelem :,} , uncompressed={ _fmt_bytes (nelem * 8 )} " )
182+ log (f" leaf{ ndim } : shape={ shape } , elements={ nelem :,} , uncompressed={ _fmt_bytes (nelem * 8 )} " )
125183 if nleaves > 10 :
126- print (f" ... ({ nleaves - 10 } more)" )
127- print (f" CTable rows: { nrows } | uncompressed table size: { _fmt_bytes (tmpl_table .nbytes )} " )
184+ log (f" ... ({ nleaves - 10 } more)" )
185+ log (f" CTable rows: { nrows } | uncompressed table size: { _fmt_bytes (tmpl_table .nbytes )} " )
128186
129187 t0 = time .perf_counter ()
130- tstore = blosc2 .TreeStore (OUTPUT_FILE , mode = "w" )
188+ tstore = blosc2 .TreeStore (output , mode = "w" )
131189
132190 try :
133191 if not no_vlmeta :
@@ -160,12 +218,12 @@ def create_store(
160218 ct = tstore [table_key ]
161219 ct .vlmeta ["description" ] = f"Level { level } CTable"
162220 ct .vlmeta ["author" ] = "blosc2"
163- ct .vlmeta ["ncols" ] = 4
221+ ct .vlmeta ["ncols" ] = tmpl_table . ncols
164222 ct .vlmeta ["has_index" ] = True
165223 ct .vlmeta ["tags_list" ] = ["benchmark" , "testing" , f"level_{ level } " ]
166224
167225 if (level + 1 ) % max (1 , nlevels // 10 ) == 0 or level == nlevels - 1 :
168- print (f" Level { level + 1 } /{ nlevels } done ({ time .perf_counter () - t0 :.2f} s so far)" )
226+ log (f" Level { level + 1 } /{ nlevels } done ({ time .perf_counter () - t0 :.2f} s so far)" )
169227 finally :
170228 tstore .close ()
171229
@@ -308,7 +366,8 @@ def main() -> None:
308366 for d in range (args .nleaves )
309367 )
310368 total_data_bytes = (
311- total_elements * 8 + args .nlevels * args .nrows * (1 + 8 + 8 + 16 ) # rough for table
369+ # rough per-row table size: bool + int64 + float64 + str + 16 numeric cols
370+ total_elements * 8 + args .nlevels * args .nrows * (1 + 8 + 8 + 16 + 16 * 8 )
312371 )
313372 file_size = os .path .getsize (OUTPUT_FILE )
314373
0 commit comments