deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
solver_gmres.cc
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2024 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13
15
17
19
20namespace internal
21{
22 namespace SolverGMRESImplementation
23 {
24 template <bool delayed_reorthogonalization, typename Number>
25 void
26 do_Tvmult_add(const unsigned int n_vectors,
27 const std::size_t locally_owned_size,
28 const Number *current_vector,
29 const std::vector<const Number *> &orthogonal_vectors,
31 {
32 unsigned int j = 0;
33
34 if (n_vectors <= 128)
35 {
36 // optimized path
37 static constexpr unsigned int n_lanes =
39
41 for (unsigned int i = 0; i < n_vectors; ++i)
42 hs[i] = 0.0;
44 correct[delayed_reorthogonalization ? 129 : 1];
45 if (delayed_reorthogonalization)
46 for (unsigned int i = 0; i < n_vectors + 1; ++i)
47 correct[i] = 0.0;
48
49 unsigned int c = 0;
50
51 constexpr unsigned int inner_batch_size =
52 delayed_reorthogonalization ? 6 : 12;
53
54 const unsigned int loop_length_c =
55 locally_owned_size / n_lanes / inner_batch_size;
56
57 // At this point, we would like to run a loop over the variable 'c'
58 // from 0 all the way to loop_length_c, and then run a loop over 'i'
59 // to work on all vectors we want to compute the inner product
60 // against. In other words, the loop layout would be:
61 //
62 // for (unsigned int c = 0; c < loop_length_c; ++c)
63 // {
64 // // do some work that only depends on the index c or the
65 // // derived index j = c * n_lanes * inner_batch_size
66 // ...
67 // for (unsigned int i = 0; i < n_vectors - 1; ++i)
68 // {
69 // // do the work with orthonormal_vectors[i][j] and
70 // // current_vector[j] to fill the summation variables
71 // }
72 // }
73 //
74 // However, this access pattern leads to relatively poor memory
75 // behavior with low performance because we would access only a few
76 // entries of n_vectors 'orthonormal_vectors[i][j]' for an index j
77 // at a time, before we pass on to the next vector i. More
78 // precisely, we access inner_batch_size * n_lanes many entries
79 // before moving on to the next vector. Modern CPUs derive a good
80 // deal of performance from so-called hardware prefetching, which is
81 // a mechanism that speculatively initiates loads from main memory
82 // that the hardware guesses will be accessed soon, in order to
83 // reduce the waiting time once the access actually happens. This
84 // data is put into fast cache memory in the meantime. Prefetching
85 // gets typically initiated when we access the entries of an array
86 // consecutively, or also when looping over the data elements of a
87 // few vectors at the same time. However, for more than around 10
88 // vectors looped over simultaneously, the hardware gets to see too
89 // many streams and the capacity of the loop stream detectors gets
90 // exceeded. As a result, the hardware will first initiate a load
91 // when the entry is actually requested by the respective code with
92 // that loop index. As it takes many clock cycles for data to arrive
93 // from memory (on the order of 200-500 clock cycles on 2024
94 // hardware, whereas caches can deliver data in 4-20 cycles and
95 // computations can be done in 3-6 cycles), and since even CPUs with
96 // good out-of-order execution capabilities can issue only a limited
97 // number of loads, the memory interface will become under-utilized,
98 // which is counter-intuitive for a loop we expect to be very
99 // memory-bandwidth heavy.
100 //
101 // The solution is to perform so-called loop blocking, see, e.g.,
102 // https://en.wikipedia.org/wiki/Loop_nest_optimization - we split
103 // the loop over the variable i into tiles (or blocks) of size 8 to
104 // make sure the hardware prefetchers are able to follow all 8
105 // streams, using a variable called 'i_block', and then run the loop
106 // over 'c' inside. Since we do not want to re-load the entries of
107 // 'current_vector' every time we work on blocks of size 8 for the
108 // 'i' variable, but want to make sure to obtained it from faster
109 // cache memory, we also tile the loop over 'c' into blocks. These
110 // blocks have size 64, which is big enough to get most of the
111 // effect of prefetchers (64 * inner_block_size * n_lanes is already
112 // several kB of data) but small enough for 'current_vector' to
113 // still be in fast cache memory for the next round of
114 // 'i_block'. The end result are thus three nested loops visible
115 // here, over 'c_block', 'i_block', and 'c', and an inner loop over
116 // i and the inner batch size for the actual work. Not the prettiest
117 // code, but giving adequate performance.
118 for (unsigned int c_block = 0; c_block < (loop_length_c + 63) / 64;
119 ++c_block)
120 for (unsigned int i_block = 0; i_block < (n_vectors + 7) / 8;
121 ++i_block)
122 for (c = c_block * 64, j = c * n_lanes * inner_batch_size;
123 c < std::min(loop_length_c, (c_block + 1) * 64);
124 ++c, j += n_lanes * inner_batch_size)
125 {
126 VectorizedArray<double> vvec[inner_batch_size];
127 for (unsigned int k = 0; k < inner_batch_size; ++k)
128 vvec[k].load(current_vector + j + k * n_lanes);
129 VectorizedArray<double> prev_vector[inner_batch_size];
130 if (delayed_reorthogonalization || i_block == 0)
131 for (unsigned int k = 0; k < inner_batch_size; ++k)
132 prev_vector[k].load(orthogonal_vectors[n_vectors - 1] +
133 j + k * n_lanes);
134
135 if (i_block == 0)
136 {
137 VectorizedArray<double> local_sum_0 =
138 prev_vector[0] * vvec[0];
139 VectorizedArray<double> local_sum_1 =
140 prev_vector[0] * prev_vector[0];
141 VectorizedArray<double> local_sum_2 = vvec[0] * vvec[0];
142 for (unsigned int k = 1; k < inner_batch_size; ++k)
143 {
144 local_sum_0 += prev_vector[k] * vvec[k];
145 if (delayed_reorthogonalization)
146 {
147 local_sum_1 += prev_vector[k] * prev_vector[k];
148 local_sum_2 += vvec[k] * vvec[k];
149 }
150 }
151 hs[n_vectors - 1] += local_sum_0;
152 if (delayed_reorthogonalization)
153 {
154 correct[n_vectors - 1] += local_sum_1;
155 correct[n_vectors] += local_sum_2;
156 }
157 }
158
159 for (unsigned int i = i_block * 8;
160 i < std::min(n_vectors - 1, (i_block + 1) * 8);
161 ++i)
162 {
163 // break the dependency chain into the field hs[i] for
164 // small sizes i by first accumulating 6 or 12 results
165 // into a local variable
167 temp.load(orthogonal_vectors[i] + j);
168 VectorizedArray<double> local_sum_0 = temp * vvec[0];
169 VectorizedArray<double> local_sum_1 =
170 delayed_reorthogonalization ? temp * prev_vector[0] :
171 0.;
172 for (unsigned int k = 1; k < inner_batch_size; ++k)
173 {
174 temp.load(orthogonal_vectors[i] + j + k * n_lanes);
175 local_sum_0 += temp * vvec[k];
176 if (delayed_reorthogonalization)
177 local_sum_1 += temp * prev_vector[k];
178 }
179 hs[i] += local_sum_0;
180 if (delayed_reorthogonalization)
181 correct[i] += local_sum_1;
182 }
183 }
184
185 c *= inner_batch_size;
186 for (; c < locally_owned_size / n_lanes; ++c, j += n_lanes)
187 {
188 VectorizedArray<double> vvec, prev_vector;
189 vvec.load(current_vector + j);
190 prev_vector.load(orthogonal_vectors[n_vectors - 1] + j);
191 hs[n_vectors - 1] += prev_vector * vvec;
192 if (delayed_reorthogonalization)
193 {
194 correct[n_vectors - 1] += prev_vector * prev_vector;
195 correct[n_vectors] += vvec * vvec;
196 }
197
198 for (unsigned int i = 0; i < n_vectors - 1; ++i)
199 {
201 temp.load(orthogonal_vectors[i] + j);
202 hs[i] += temp * vvec;
203 if (delayed_reorthogonalization)
204 correct[i] += temp * prev_vector;
205 }
206 }
207
208 for (unsigned int i = 0; i < n_vectors; ++i)
209 {
210 h(i) += hs[i].sum();
211 if (delayed_reorthogonalization)
212 h(i + n_vectors) += correct[i].sum();
213 }
214 if (delayed_reorthogonalization)
215 h(n_vectors + n_vectors) += correct[n_vectors].sum();
216 }
217
218 // remainder loop of optimized path or non-optimized path (if
219 // n>128)
220 for (; j < locally_owned_size; ++j)
221 {
222 const double vvec = current_vector[j];
223 const double prev_vector = orthogonal_vectors[n_vectors - 1][j];
224 h(n_vectors - 1) += prev_vector * vvec;
225 if (delayed_reorthogonalization)
226 {
227 h(n_vectors + n_vectors - 1) += prev_vector * prev_vector;
228 h(n_vectors + n_vectors) += vvec * vvec;
229 }
230 for (unsigned int i = 0; i < n_vectors - 1; ++i)
231 {
232 const double temp = orthogonal_vectors[i][j];
233 h(i) += temp * vvec;
234 if (delayed_reorthogonalization)
235 h(n_vectors + i) += temp * prev_vector;
236 }
237 }
238 }
239
240
241
242 template <bool delayed_reorthogonalization, typename Number>
243 double
244 do_subtract_and_norm(const unsigned int n_vectors,
245 const std::size_t locally_owned_size,
246 const std::vector<const Number *> &orthogonal_vectors,
247 const Vector<double> &h,
248 Number *current_vector)
249 {
250 double norm_vv_temp = 0;
251
252 Number *previous_vector =
253 const_cast<Number *>(orthogonal_vectors[n_vectors - 1]);
254 const double inverse_norm_previous =
255 delayed_reorthogonalization ? 1. / h(n_vectors + n_vectors - 1) : 0.;
256 const double scaling_factor_vv =
257 delayed_reorthogonalization ?
258 (h(n_vectors + n_vectors) > 0.0 ?
259 inverse_norm_previous / h(n_vectors + n_vectors) :
260 inverse_norm_previous / h(n_vectors + n_vectors - 1)) :
261 0.;
262 const double last_factor = h(n_vectors - 1);
263
264 VectorizedArray<double> norm_vv_temp_vectorized = 0.0;
265
266 static constexpr unsigned int n_lanes = VectorizedArray<double>::size();
267 constexpr unsigned int inner_batch_size =
268 delayed_reorthogonalization ? 6 : 12;
269
270 // As for the do_Tvmult_add loop above, we perform loop blocking on both
271 // the 'i' and 'c' variable to help hardware prefetchers to perform
272 // adequately, and get three nested loops here plus the inner loops. See
273 // the extensive comments above for the full rationale.
274 unsigned int j = 0;
275 unsigned int c = 0;
276 const unsigned int loop_length_c =
277 locally_owned_size / n_lanes / inner_batch_size;
278 const unsigned int loop_length_i = (n_vectors + 7) / 8;
279 for (unsigned int c_block = 0; c_block < (loop_length_c + 63) / 64;
280 ++c_block)
281 for (unsigned int i_block = 0; i_block < (n_vectors + 7) / 8; ++i_block)
282 for (c = c_block * 64, j = c * n_lanes * inner_batch_size;
283 c < std::min(loop_length_c, (c_block + 1) * 64);
284 ++c, j += n_lanes * inner_batch_size)
285 {
286 VectorizedArray<double> temp[inner_batch_size];
287 for (unsigned int k = 0; k < inner_batch_size; ++k)
288 temp[k].load(current_vector + j + k * n_lanes);
289 VectorizedArray<double> prev_vector[inner_batch_size];
290 if (delayed_reorthogonalization)
291 for (unsigned int k = 0; k < inner_batch_size; ++k)
292 prev_vector[k].load(previous_vector + j + k * n_lanes);
293
294 for (unsigned int i = i_block * 8;
295 i < std::min(n_vectors - 1, (i_block + 1) * 8);
296 ++i)
297 {
298 const double factor = h(i);
299 const double correction_factor =
300 (delayed_reorthogonalization ? h(n_vectors + i) : 0.0);
301 for (unsigned int k = 0; k < inner_batch_size; ++k)
302 {
304 vec.load(orthogonal_vectors[i] + j + k * n_lanes);
305 temp[k] -= factor * vec;
306 if (delayed_reorthogonalization)
307 prev_vector[k] -= correction_factor * vec;
308 }
309 }
310
311 if (delayed_reorthogonalization)
312 {
313 if (i_block + 1 == loop_length_i)
314 for (unsigned int k = 0; k < inner_batch_size; ++k)
315 {
316 prev_vector[k] = prev_vector[k] * inverse_norm_previous;
317 prev_vector[k].store(previous_vector + j + k * n_lanes);
318 temp[k] -= last_factor * prev_vector[k];
319 temp[k] = temp[k] * scaling_factor_vv;
320 temp[k].store(current_vector + j + k * n_lanes);
321 }
322 else
323 for (unsigned int k = 0; k < inner_batch_size; ++k)
324 {
325 prev_vector[k].store(previous_vector + j + k * n_lanes);
326 temp[k].store(current_vector + j + k * n_lanes);
327 }
328 }
329 else
330 {
331 if (i_block + 1 == loop_length_i)
332 for (unsigned int k = 0; k < inner_batch_size; ++k)
333 {
334 prev_vector[k].load(previous_vector + j + k * n_lanes);
335 temp[k] -= last_factor * prev_vector[k];
336 temp[k].store(current_vector + j + k * n_lanes);
337 norm_vv_temp_vectorized += temp[k] * temp[k];
338 }
339 else
340 for (unsigned int k = 0; k < inner_batch_size; ++k)
341 temp[k].store(current_vector + j + k * n_lanes);
342 }
343 }
344
345 c *= inner_batch_size;
346 for (; c < locally_owned_size / n_lanes; ++c, j += n_lanes)
347 {
348 VectorizedArray<double> temp, prev_vector;
349 temp.load(current_vector + j);
350 prev_vector.load(previous_vector + j);
351 if (!delayed_reorthogonalization)
352 temp -= h(n_vectors - 1) * prev_vector;
353
354 for (unsigned int i = 0; i < n_vectors - 1; ++i)
355 {
357 vec.load(orthogonal_vectors[i] + j);
358 temp -= h(i) * vec;
359 if (delayed_reorthogonalization)
360 prev_vector -= h(n_vectors + i) * vec;
361 }
362
363 if (delayed_reorthogonalization)
364 {
365 prev_vector = prev_vector * inverse_norm_previous;
366 prev_vector.store(previous_vector + j);
367 temp -= h(n_vectors - 1) * prev_vector;
368 temp = temp * scaling_factor_vv;
369 temp.store(current_vector + j);
370 }
371 else
372 {
373 temp.store(current_vector + j);
374 norm_vv_temp_vectorized += temp * temp;
375 }
376 }
377
378 if (!delayed_reorthogonalization)
379 norm_vv_temp += norm_vv_temp_vectorized.sum();
380
381 for (; j < locally_owned_size; ++j)
382 {
383 double temp = current_vector[j];
384 double prev_vector = previous_vector[j];
385 if (delayed_reorthogonalization)
386 {
387 for (unsigned int i = 0; i < n_vectors - 1; ++i)
388 {
389 const double vec = orthogonal_vectors[i][j];
390 temp -= h(i) * vec;
391 prev_vector -= h(n_vectors + i) * vec;
392 }
393 prev_vector *= inverse_norm_previous;
394 previous_vector[j] = prev_vector;
395 temp -= h(n_vectors - 1) * prev_vector;
396 temp *= scaling_factor_vv;
397 }
398 else
399 {
400 temp -= h(n_vectors - 1) * prev_vector;
401 for (unsigned int i = 0; i < n_vectors - 1; ++i)
402 temp -= h(i) * orthogonal_vectors[i][j];
403 norm_vv_temp += temp * temp;
404 }
405 current_vector[j] = temp;
406 }
407
408 return norm_vv_temp;
409 }
410
411
412
413 template <typename Number>
414 void
415 do_add(const unsigned int n_vectors,
416 const std::size_t locally_owned_size,
417 const std::vector<const Number *> &tmp_vectors,
418 const Vector<double> &h,
419 const bool zero_out,
420 Number *output)
421 {
422 static constexpr unsigned int n_lanes = VectorizedArray<double>::size();
423 constexpr unsigned int inner_batch_size = 12;
424
425 unsigned int j = 0;
426 unsigned int c = 0;
427 for (; c < locally_owned_size / n_lanes / inner_batch_size;
428 ++c, j += n_lanes * inner_batch_size)
429 {
430 VectorizedArray<double> temp[inner_batch_size];
431 if (zero_out)
432 for (VectorizedArray<double> &a : temp)
433 a = {};
434 else
435 for (unsigned int k = 0; k < inner_batch_size; ++k)
436 temp[k].load(output + j + k * n_lanes);
437
438 for (unsigned int i = 0; i < n_vectors; ++i)
439 {
440 const double h_i = h(i);
441 for (unsigned int k = 0; k < inner_batch_size; ++k)
442 {
444 v_ij.load(tmp_vectors[i] + j + k * n_lanes);
445 temp[k] += v_ij * h_i;
446 }
447 }
448
449 for (unsigned int k = 0; k < inner_batch_size; ++k)
450 temp[k].store(output + j + k * n_lanes);
451 }
452
453 c *= inner_batch_size;
454 for (; c < locally_owned_size / n_lanes; ++c, j += n_lanes)
455 {
456 VectorizedArray<double> temp = {};
457 if (!zero_out)
458 temp.load(output + j);
459
460 for (unsigned int i = 0; i < n_vectors; ++i)
461 {
463 v_ij.load(tmp_vectors[i] + j);
464 temp += v_ij * h(i);
465 }
466
467 temp.store(output + j);
468 }
469
470 for (; j < locally_owned_size; ++j)
471 {
472 double temp = zero_out ? 0.0 : output[j];
473 for (unsigned int i = 0; i < n_vectors; ++i)
474 temp += tmp_vectors[i][j] * h(i);
475 output[j] = temp;
476 }
477 }
478 } // namespace SolverGMRESImplementation
479} // namespace internal
480
481#include "lac/solver_gmres.inst"
482
Number sum() const
void store(OtherNumber *ptr) const
void load(const OtherNumber *ptr)
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
types::global_dof_index locally_owned_size
Definition mpi.cc:821
void do_Tvmult_add(const unsigned int n_vectors, const std::size_t locally_owned_size, const Number *current_vector, const std::vector< const Number * > &orthogonal_vectors, Vector< double > &h)
double do_subtract_and_norm(const unsigned int n_vectors, const std::size_t locally_owned_size, const std::vector< const Number * > &orthogonal_vectors, const Vector< double > &h, Number *current_vector)
void do_add(const unsigned int n_vectors, const std::size_t locally_owned_size, const std::vector< const Number * > &tmp_vectors, const Vector< double > &h, const bool zero_out, Number *output)
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)