33
44//! PCO probes retain every decoded page and index the compacted, non-null value positions.
55
6- use std:: ops:: Range ;
7-
8- use pco:: data_types:: Number ;
9- use pco:: data_types:: NumberType ;
10- use pco:: match_number_enum;
11- use pco:: wrapped:: FileDecompressor ;
126use vortex_array:: ArrayView ;
137use vortex_array:: ExecutionCtx ;
14- use vortex_array:: IntoArray ;
15- use vortex_array:: arrays:: PrimitiveArray ;
16- use vortex_array:: dtype:: NativePType ;
17- use vortex_array:: dtype:: half;
188use vortex_array:: match_each_native_ptype;
199use vortex_array:: scalar:: Scalar ;
20- use vortex_array:: validity:: Validity ;
21- use vortex_buffer:: BufferMut ;
22- use vortex_error:: VortexExpect ;
2310use vortex_error:: VortexResult ;
24- use vortex_mask :: Mask ;
11+ use vortex_error :: vortex_ensure ;
2512
2613use crate :: Pco ;
27- use crate :: PcoArrayExt ;
28- use crate :: array:: number_type_from_ptype;
29- use crate :: array:: vortex_err_from_pco;
30-
31- const RANK_STRIDE : usize = 512 ;
14+ use crate :: access:: AccessState ;
3215
3316/// State retained by repeated PCO probes. Construction performs no allocation.
3417///
35- /// The mask and rank index use unsliced logical rows; page boundaries use compacted value
36- /// positions. Every page decoded so far is retained, so the state grows with the pages touched
37- /// and never beyond the decompressed array.
18+ /// Decoded pages and their local validity are retained as touched, up to the full array.
19+ /// Legacy files additionally retain a full validity mask and its sparse rank index.
3820#[ derive( Default ) ]
3921pub struct PcoProbeState {
40- validity : Option < Mask > ,
41- rank : Vec < usize > ,
42- pages : Vec < Page > ,
43- /// The page the previous probe landed in, `None` until the first probe resolves one.
44- last_page : Option < usize > ,
45- }
46-
47- struct Page {
48- values : Range < usize > ,
49- chunk : usize ,
50- decoded : Option < PrimitiveArray > ,
22+ access : AccessState ,
5123}
5224
5325pub ( crate ) fn scalar_at (
@@ -57,110 +29,47 @@ pub(crate) fn scalar_at(
5729 ctx : & mut ExecutionCtx ,
5830) -> VortexResult < Scalar > {
5931 let Some ( state) = state else {
60- let validity = array. unsliced_validity ( ) ;
61- return array
62- . _slice ( index, index + 1 )
63- . decompress ( & validity, ctx) ?
64- . into_array ( )
65- . execute_scalar ( 0 , ctx) ;
66- } ;
67- let mask = match & mut state. validity {
68- Some ( mask) => mask,
69- slot @ None => slot. insert (
70- array
71- . unsliced_validity ( )
72- . execute_mask ( array. unsliced_n_rows ( ) , ctx) ?,
73- ) ,
74- } ;
75- let logical_index = array. slice_start ( ) + index;
76-
77- let value_index = match mask {
78- Mask :: AllTrue ( _) => logical_index,
79- Mask :: AllFalse ( _) => return Ok ( Scalar :: null ( array. dtype ( ) . clone ( ) ) ) ,
80- Mask :: Values ( values) => {
81- let bits = values. bit_buffer ( ) ;
82- if !bits. value ( logical_index) {
83- return Ok ( Scalar :: null ( array. dtype ( ) . clone ( ) ) ) ;
84- }
85- if state. rank . is_empty ( ) {
86- state. rank . reserve ( bits. len ( ) . div_ceil ( RANK_STRIDE ) ) ;
87- let mut count = 0 ;
88- for start in ( 0 ..bits. len ( ) ) . step_by ( RANK_STRIDE ) {
89- state. rank . push ( count) ;
90- count += bits. count_range ( start, ( start + RANK_STRIDE ) . min ( bits. len ( ) ) ) ;
91- }
92- }
93- let block = logical_index / RANK_STRIDE ;
94- state. rank [ block] + bits. count_range ( block * RANK_STRIDE , logical_index)
95- }
32+ return scalar_at_once ( array, index, ctx) ;
9633 } ;
97-
98- if state. pages . is_empty ( ) {
99- let mut start = 0 ;
100- for ( chunk, metadata) in array. metadata . chunks . iter ( ) . enumerate ( ) {
101- for page in & metadata. pages {
102- let end = start + page. n_values as usize ;
103- state. pages . push ( Page {
104- values : start..end,
105- chunk,
106- decoded : None ,
107- } ) ;
108- start = end;
109- }
110- }
34+ if state. access . validated_all_null ( ) {
35+ vortex_ensure ! (
36+ index < array. len( ) ,
37+ "Pco scalar index exceeds visible array bounds"
38+ ) ;
39+ return Ok ( Scalar :: null ( array. dtype ( ) . clone ( ) ) ) ;
11140 }
112- let page_index = match state. last_page {
113- Some ( last) if state. pages [ last] . values . contains ( & value_index) => last,
114- _ => state
115- . pages
116- . partition_point ( |page| page. values . end <= value_index) ,
117- } ;
118- state. last_page = Some ( page_index) ;
119- let page = state
120- . pages
121- . get_mut ( page_index)
122- . vortex_expect ( "PCO pages cover every valid value index" ) ;
123- let values = match & mut page. decoded {
124- Some ( decoded) => decoded,
125- slot @ None => {
126- let decoded = match_number_enum ! (
127- number_type_from_ptype( array. dtype( ) . as_ptype( ) ) ,
128- NumberType <T > => {
129- decode_page:: <T >( array, page. chunk, page. values. len( ) , array. pages[ page_index] . as_slice( ) , ctx) ?
130- }
131- ) ;
132- slot. insert ( decoded)
133- }
134- } ;
135- Ok ( match_each_native_ptype ! ( values. ptype( ) , |T | {
136- Scalar :: primitive(
137- values. as_slice:: <T >( ) [ value_index - page. values. start] ,
138- array. dtype( ) . nullability( ) ,
139- )
140- } ) )
41+ let row = array. slice_start ( ) + index;
42+ if let Some ( value) = state. access . cached_scalar ( array, row) {
43+ return Ok ( value) ;
44+ }
45+ scalar_at_uncached ( array, row, state, ctx)
14146}
14247
143- fn decode_page < T : Number + NativePType > (
48+ // Keep the large temporary cache out of the retained-probe hit frame. Stateless
49+ // probes and cache misses are ordinary workloads, so neither helper is cold.
50+ #[ inline( never) ]
51+ fn scalar_at_once (
14452 array : ArrayView < ' _ , Pco > ,
145- chunk : usize ,
146- n_values : usize ,
147- buffer : & [ u8 ] ,
53+ index : usize ,
14854 ctx : & mut ExecutionCtx ,
149- ) -> VortexResult < PrimitiveArray > {
150- let ( file, _) =
151- FileDecompressor :: new ( array. metadata . header . as_slice ( ) ) . map_err ( vortex_err_from_pco) ?;
152- let ( mut chunk, _) = file
153- . chunk_decompressor :: < T , _ > ( array. chunk_metas [ chunk] . as_ref ( ) )
154- . map_err ( vortex_err_from_pco) ?;
155- let mut decoder = chunk
156- . page_decompressor ( buffer, n_values)
157- . map_err ( vortex_err_from_pco) ?;
158- let mut values = BufferMut :: < T > :: with_capacity_in ( n_values, ctx. allocator ( ) . clone ( ) ) ;
159- // SAFETY: the buffer reserves `n_values` elements, and the page decompressor was built for
160- // exactly that count, so `read` writes every element before anything observes it.
161- unsafe { values. set_len ( n_values) } ;
162- decoder. read ( & mut values) . map_err ( vortex_err_from_pco) ?;
163- Ok ( PrimitiveArray :: new ( values. freeze ( ) , Validity :: NonNullable ) )
55+ ) -> VortexResult < Scalar > {
56+ scalar_at ( array, index, Some ( & mut PcoProbeState :: default ( ) ) , ctx)
57+ }
58+
59+ #[ inline( never) ]
60+ fn scalar_at_uncached (
61+ array : ArrayView < ' _ , Pco > ,
62+ row : usize ,
63+ state : & mut PcoProbeState ,
64+ ctx : & mut ExecutionCtx ,
65+ ) -> VortexResult < Scalar > {
66+ let Some ( ( page, value) ) = state. access . resolve ( array, row, ctx) ? else {
67+ return Ok ( Scalar :: null ( array. dtype ( ) . clone ( ) ) ) ;
68+ } ;
69+ let values = state. access . values ( array, page, ctx) ?;
70+ Ok ( match_each_native_ptype ! ( values. ptype( ) , |T | {
71+ Scalar :: primitive( values. as_slice:: <T >( ) [ value] , array. dtype( ) . nullability( ) )
72+ } ) )
16473}
16574
16675#[ cfg( test) ]
0 commit comments