1818
1919enum { BLT_UNINIT , BLT_ROWS , BLT_READY };
2020
21+ #define BLT_INV_CACHE_SIZE 32
22+
2123// static int minrowsize = 1000;
2224// static double small = 1.0e-10;
2325// static double abssmall = 1.0e-30;
@@ -44,7 +46,10 @@ static int blt_get_number_of_threads()
4446 return threadcount ;
4547}
4648
47- static void blt_load_col_cache_mt ( bltmatrix * blt , double * * tmpcol , double * * sumcol ,
49+ /* tmpcol and sumcol are row-major flat arrays: [row * stride + col_slot].
50+ stride == threadcount (maximum column slots per batch). */
51+ static void blt_load_col_cache_mt ( bltmatrix * blt , double * tmpcol , double * sumcol ,
52+ const int stride ,
4853 int * dosum , int iget , int nget , int isave , int nsave )
4954{
5055
@@ -57,6 +62,8 @@ static void blt_load_col_cache_mt( bltmatrix *blt, double **tmpcol, double **sum
5762 int c0 ;
5863 double * row = blt -> row [i ].address ;
5964 double * r ;
65+ double * sc_i = sumcol + i * stride ;
66+ double * tc_i = tmpcol + i * stride ;
6067
6168 /* If there are rows to be saved, then do so */
6269
@@ -65,7 +72,7 @@ static void blt_load_col_cache_mt( bltmatrix *blt, double **tmpcol, double **sum
6572 for ( c0 = dosum [i ], r = row + isave + c0 - col0 ; c0 < nsave ; c0 ++ , r ++ )
6673 {
6774 if ( isave + c0 > i ) break ;
68- * r = sumcol [c0 ][ i ];
75+ * r = sc_i [c0 ];
6976 }
7077 }
7178
@@ -74,129 +81,259 @@ static void blt_load_col_cache_mt( bltmatrix *blt, double **tmpcol, double **sum
7481 dosum [i ] = c0 = col0 < iget ? 0 : col0 - iget ;
7582 if ( c0 >= nget ) continue ;
7683
77- /* Retrieve the cols and initiallize the summation */
84+ /* Retrieve the cols and initialise the summation */
7885
7986 for ( r = row + iget + c0 - col0 ; c0 < nget ; c0 ++ , r ++ )
8087 {
8188 if ( iget + c0 > i ) break ;
82- tmpcol [c0 ][ i ] = * r ;
83- sumcol [c0 ][ i ] = 0.0 ;
89+ tc_i [c0 ] = * r ;
90+ sc_i [c0 ] = 0.0 ;
8491 }
8592 }
8693}
8794
88- static void blt_chol_inv_mt_sumcol ( bltmatrix * blt , int * dosum , double * sumcol , double * tmpcol , int i1 , int c )
95+ /* Accumulates the contribution of BLT rows [j_start, j_end) into sumcol via
96+ * the Cholesky inversion recurrence.
97+ *
98+ * dosum[r] – index of the first active column slot for BLT row r.
99+ * tmpcol – row-major flat array [row * stride + col_slot], read-only.
100+ * sumcol – row-major flat array [row * stride + col_slot]; this thread
101+ * owns rows k in [j_start, j_end) and writes there directly
102+ * (no race — exclusive ownership).
103+ * spill – private row-major flat array [(k - spill_base) * stride + col_slot]
104+ * for cross-thread writes (k < j_start); null for thread 0 (unused).
105+ * Caller accumulates into sumcol after all threads join.
106+ * stride – column slots per row (= threadcount).
107+ * spill_base – base row index for spill indexing (= i1+1 for the batch).
108+ *
109+ * Returns the minimum k written into spill, or j_start if no spill writes
110+ * occurred, so the caller can bound the accumulation to [k_min, j_start).
111+ */
112+ static int blt_chol_inv_mt_rowrange ( bltmatrix * blt , const int * dosum ,
113+ const double * tmpcol ,
114+ double * sumcol , double * spill ,
115+ const int spill_base ,
116+ const int ncols , const int stride ,
117+ const int i1 ,
118+ const int j_start , const int j_end )
89119{
90- int nrow = blt -> nrow ;
91- for ( int j = i1 + 1 ; j < nrow ; j ++ )
120+ int k_min = j_start ;
121+ for ( int j = j_start ; j < j_end ; j ++ )
92122 {
93123 int col0 = blt -> row [j ].col ;
94124 double * row = blt -> row [j ].address ;
95125
96126 if ( col0 < i1 + 1 )
97127 {
98- row += i1 + 1 - col0 ;
128+ row += i1 + 1 - col0 ;
99129 col0 = i1 + 1 ;
100130 }
101131
102- double sj = 0 ;
103- for ( ; col0 <= j ; col0 ++ , row ++ )
132+ const double * tc_j = tmpcol + j * stride ;
133+ double * sc_j = sumcol + j * stride ;
134+
135+ for ( int k = col0 ; k <= j ; k ++ , row ++ )
104136 {
105- /* Sum effect of (j,col0) element, value at *row */
106- if ( c >= dosum [col0 ] && c >= dosum [j ] )
137+ double elem = * row ;
138+ int c_start = dosum [k ] > dosum [j ] ? dosum [k ] : dosum [j ];
139+ const double * tc_k = tmpcol + k * stride ;
140+ if ( k >= j_start )
141+ {
142+ double * sc_k = sumcol + k * stride ;
143+ if ( j != k )
107144 {
108- sumcol [col0 ] -= * row * tmpcol [j ];
109- if ( j != col0 )
145+ for ( int c = c_start ; c < ncols ; c ++ )
110146 {
111- sj -= * row * tmpcol [col0 ];
147+ sc_k [c ] -= elem * tc_j [c ];
148+ sc_j [c ] -= elem * tc_k [c ];
112149 }
113150 }
151+ else
152+ {
153+ for ( int c = c_start ; c < ncols ; c ++ )
154+ sc_k [c ] -= elem * tc_j [c ];
155+ }
156+ }
157+ else /* k < j_start: cross-thread, accumulate into spill */
158+ {
159+ if ( k < k_min ) k_min = k ;
160+ double * sp_k = spill + (k - spill_base ) * stride ;
161+ for ( int c = c_start ; c < ncols ; c ++ )
162+ {
163+ sp_k [c ] -= elem * tc_j [c ];
164+ sc_j [c ] -= elem * tc_k [c ];
165+ }
166+ }
114167 }
115- sumcol [j ] += sj ;
116168 }
169+ return k_min ;
117170}
118171
119172void blt_chol_inv_mt ( bltmatrix * blt )
120173{
121- int nrow ;
122- int nsave ;
123- int i ,i0 ,i1 ,c ,c1 ,j ;
124- double * tmp ;
125- double * * tmpcol ;
126- double * * sumcol ;
127- int * dosum ;
128- long ndone ;
129-
130- int threadcount = blt_get_number_of_threads ();
174+ const int threadcount = blt_get_number_of_threads ();
131175 if ( threadcount < 2 )
132176 {
133177 blt_chol_inv (blt );
134178 return ;
135179 }
136180
137- nrow = blt -> nrow ;
138-
139- tmp = (double * ) check_malloc ( 2 * nrow * threadcount * sizeof (double ) );
140- tmpcol = (double * * ) check_malloc ( 2 * threadcount * sizeof (double * ) );
141- sumcol = tmpcol + threadcount ;
181+ const int nrow = blt -> nrow ;
182+ const int stride = BLT_INV_CACHE_SIZE ;
183+
184+ /* Row-major layout: [row * stride + col_slot], so the inner loop over
185+ col_slot is sequential — matches ST commit f4a828c2. stride is fixed at
186+ BLT_INV_CACHE_SIZE (not threadcount) so the inner c loop runs 32 iterations
187+ regardless of thread count, amortising per-k overhead and keeping
188+ arithmetic intensity high. */
189+ std ::vector < double > tmp (2 * nrow * stride );
190+ double * const tmpcol_flat = tmp .data ();
191+ double * const sumcol_flat = tmp .data () + nrow * stride ;
192+
193+ std ::vector < int > dosum (nrow );
194+
195+ /* work_prefix[j] = cumulative element count for rows 0..j-1, used to
196+ partition rows by work rather than by count so threads get equal-effort
197+ ranges. Uses unclamped blt->row[j].col (ignores spill_base clamping),
198+ which overestimates actual work for early batches — safe since it only
199+ causes cut-points to land slightly early, never out of range. */
200+ std ::vector < long > work_prefix (nrow + 1 );
201+ work_prefix [0 ] = 0 ;
202+ for ( int j = 0 ; j < nrow ; j ++ )
203+ work_prefix [j + 1 ] = work_prefix [j ] + j - blt -> row [j ].col ;
204+
205+ /* Global work-balanced partition: j_splits[c] is the first row of thread c
206+ when spill_base==0. Computed once here and clamped per-batch. The spill
207+ buffer must be sized from these actual cut-points — a count-equal split
208+ would underallocate when skewed work pushes a cut-point beyond its
209+ count-equal equivalent. */
210+ std ::vector < int > j_splits (threadcount + 1 );
211+ j_splits [0 ] = 0 ;
212+ j_splits [threadcount ] = nrow ;
213+ {
214+ const long total_work = work_prefix [nrow ];
215+ for ( int c = 1 ; c < threadcount ; c ++ )
216+ {
217+ const long target = (long )c * total_work / threadcount ;
218+ int lo = j_splits [c - 1 ], hi = nrow ;
219+ while ( lo < hi )
220+ {
221+ const int mid = lo + (hi - lo ) / 2 ;
222+ if ( work_prefix [mid ] < target ) lo = mid + 1 ;
223+ else hi = mid ;
224+ }
225+ j_splits [c ] = lo ;
226+ }
227+ }
142228
143- for ( i = 0 ; i < threadcount ; i ++ )
229+ /* spill_buf holds per-thread spill accumulators for cross-thread writes
230+ (k < j_start for thread t). Thread t's spill covers rows [0, j_splits[t])
231+ at most, each row holding stride col slots. Only O(bandwidth) rows are
232+ ever written; the rest stay zero. Thread 0 has no spill and its pointer
233+ is left null. */
234+ long spill_total = 0 ;
235+ for ( int c = 1 ; c < threadcount ; c ++ )
236+ spill_total += (long )j_splits [c ] * stride ;
237+ std ::vector < double > spill_buf (spill_total , 0.0 );
238+ std ::vector < double * > thread_spill (threadcount , nullptr );
144239 {
145- tmpcol [i ] = tmp + nrow * i ;
146- sumcol [i ] = tmp + nrow * (i + threadcount );
240+ double * ptr = spill_buf .data ();
241+ for ( int i = 1 ; i < threadcount ; i ++ )
242+ {
243+ thread_spill [i ] = ptr ;
244+ ptr += (long )j_splits [i ] * stride ;
245+ }
147246 }
148- dosum = (int * ) check_malloc ( nrow * sizeof (int ) );
149247
150248 init_progress_meter ( blt -> nelement );
151249
152- ndone = 0 ;
153- nsave = 0 ;
250+ long ndone = 0 ;
251+ int nsave = 0 ;
154252
155- for ( i1 = nrow - 1 , i0 = nrow - threadcount ;
253+ for ( int i1 = nrow - 1 , i0 = nrow - BLT_INV_CACHE_SIZE ;
156254 i1 >= 0 ;
157- i1 = i0 - 1 , i0 -= threadcount )
255+ i1 = i0 - 1 , i0 -= BLT_INV_CACHE_SIZE )
158256 {
159-
160257 if ( i0 < 0 ) i0 = 0 ;
161258
162259 /* Save the cached row data and update with the new values ... */
163260
164- blt_load_col_cache_mt ( blt , tmpcol , sumcol , dosum , i0 , i1 - i0 + 1 , i1 + 1 , nsave );
261+ blt_load_col_cache_mt ( blt , tmpcol_flat , sumcol_flat , stride , dosum .data (),
262+ i0 , i1 - i0 + 1 , i1 + 1 , nsave );
165263 nsave = i1 - i0 + 1 ;
166264
167- /* Sum the data for the rows after i0 into the summation */
265+ /* Sum the data for the rows after i1 into the summation.
266+ Clamp the global work-balanced splits to spill_base for this batch. */
267+
268+ const int spill_base = i1 + 1 ;
269+ std ::vector < int > j_starts (threadcount + 1 );
270+ j_starts [0 ] = spill_base ;
271+ j_starts [threadcount ] = nrow ;
272+ for ( int c = 1 ; c < threadcount ; c ++ )
273+ j_starts [c ] = j_splits [c ] > spill_base ? j_splits [c ] : spill_base ;
274+
275+ std ::vector < int > k_mins (threadcount );
276+ {
277+ std ::vector < std ::thread > threads ;
278+ for ( int c = 0 ; c < threadcount ; c ++ )
279+ {
280+ const int j_start = j_starts [c ];
281+ const int j_end = j_starts [c + 1 ];
282+ double * const spill_c = thread_spill [c ];
283+ threads .emplace_back ( [= , & k_mins ]() {
284+ k_mins [c ] = blt_chol_inv_mt_rowrange (
285+ blt , dosum .data (), tmpcol_flat ,
286+ sumcol_flat , spill_c ,
287+ spill_base , nsave , stride , i1 , j_start , j_end );
288+ });
289+ }
290+ for ( auto & t : threads ) t .join ();
291+ }
168292
169- std ::vector < std ::thread > threads ;
170- for ( c = 0 ; c < nsave ; c ++ )
293+ /* Accumulate each thread's spill into sumcol and re-zero for the next
294+ batch. Thread 0 has no spill (j_start_0 = spill_base, so k >= j_start
295+ always). For threads 1..threadcount-1, only [k_mins[c], j_start_c)
296+ was written — O(bandwidth) rows rather than O(nrow). */
297+ for ( int c = 1 ; c < threadcount ; c ++ )
171298 {
172- threads .emplace_back (std ::thread ( blt_chol_inv_mt_sumcol ,
173- blt , dosum , sumcol [c ], tmpcol [c ], i1 , c ));
299+ double * const sp = thread_spill [c ];
300+ for ( int j = k_mins [c ]; j < j_starts [c ]; j ++ )
301+ {
302+ double * const sc_j = sumcol_flat + j * stride ;
303+ double * const sp_j = sp + (j - spill_base ) * stride ;
304+ for ( int c1 = 0 ; c1 < nsave ; c1 ++ )
305+ {
306+ sc_j [c1 ] += sp_j [c1 ];
307+ sp_j [c1 ] = 0.0 ;
308+ }
309+ }
174310 }
175-
176- for (auto & t : threads ){ t .join (); }
177311
178312 /* Now process the cached columns to generate the inverse in
179313 sumcol */
180314
181- for ( c = nsave ; c -- ; )
315+ for ( int c = nsave ; c -- ; )
182316 {
183- double sc ;
184- int ic = i0 + c ;
185- for ( j = nrow - 1 ; j > ic ; j -- )
317+ const int ic = i0 + c ;
318+ double * const sc_ic = sumcol_flat + ic * stride ;
319+ double * const tc_ic = tmpcol_flat + ic * stride ;
320+ for ( int j = nrow - 1 ; j > ic ; j -- )
186321 {
187322 /* Calculate the new element [ic,j] in sumcol */
188323 if ( c < dosum [j ] ) continue ;
189- sc = sumcol [c ][j ];
190- sc /= tmpcol [c ][ic ];
191- sumcol [c ][j ] = sc ;
324+ double * const sc_j = sumcol_flat + j * stride ;
325+ double * const tc_j = tmpcol_flat + j * stride ;
326+ double sc = sc_j [c ];
327+ sc /= tc_ic [c ];
328+ sc_j [c ] = sc ;
192329 ndone ++ ;
193330 /* Update the sums affected by this element (ic,j) */
194- for ( c1 = dosum [j ]; c1 < c ; c1 ++ )
331+ for ( int c1 = dosum [j ]; c1 < c ; c1 ++ )
195332 {
196333 if ( c1 >= dosum [ic ] )
197334 {
198- sumcol [c1 ][ ic ] -= sc * tmpcol [c1 ][ j ];
199- sumcol [c1 ][ j ] -= sc * tmpcol [c1 ][ ic ];
335+ sc_ic [c1 ] -= sc * tc_j [c1 ];
336+ sc_j [c1 ] -= sc * tc_ic [c1 ];
200337 }
201338 }
202339 }
@@ -206,17 +343,19 @@ void blt_chol_inv_mt( bltmatrix *blt )
206343 sum in FPU, hence improving accuracy, and accuracy is more
207344 critical for diagonal element than for others. */
208345
209- sc = 1.0 /tmpcol [ c ][ ic ];
210- for ( j = ic + 1 ; j < nrow ; j ++ )
346+ double sc = 1.0 /tc_ic [ c ];
347+ for ( int j = ic + 1 ; j < nrow ; j ++ )
211348 {
212- if ( c >= dosum [j ] ) sc -= sumcol [c ][j ] * tmpcol [c ][j ];
349+ double * const sc_j = sumcol_flat + j * stride ;
350+ double * const tc_j = tmpcol_flat + j * stride ;
351+ if ( c >= dosum [j ] ) sc -= sc_j [c ] * tc_j [c ];
213352 }
214- sc /= tmpcol [ c ][ ic ];
215- sumcol [ c ][ ic ] = sc ;
353+ sc /= tc_ic [ c ];
354+ sc_ic [ c ] = sc ;
216355
217- for ( c1 = dosum [ic ]; c1 < c ; c1 ++ )
356+ for ( int c1 = dosum [ic ]; c1 < c ; c1 ++ )
218357 {
219- sumcol [c1 ][ ic ] -= sc * tmpcol [c1 ][ ic ];
358+ sc_ic [c1 ] -= sc * tc_ic [c1 ];
220359 }
221360 }
222361
@@ -225,10 +364,7 @@ void blt_chol_inv_mt( bltmatrix *blt )
225364
226365 /* Save the last cached columns back again... */
227366
228- blt_load_col_cache_mt ( blt , tmpcol , sumcol , dosum , 0 , 0 , 0 , nsave );
367+ blt_load_col_cache_mt ( blt , tmpcol_flat , sumcol_flat , stride , dosum .data (),
368+ 0 , 0 , 0 , nsave );
229369 end_progress_meter ();
230-
231- check_free ( tmpcol );
232- check_free ( tmp );
233- check_free ( dosum );
234370}
0 commit comments