Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Новиков. Магистерская диссертация.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
523.12 Кб
Скачать

8.3 Расчёт невязки

Расчёт невязки является операцией по реализации очень похожей на операцию сглаживания, поэтому работа с граничными условиями характерная для сглаживания характерна и для нее.

Невязка рассчитывается по формуле:

, т.е.

Где количество соседей, считается так же, как в сглаживателе. Сокращение слагаемых происходит тем же образом.

8.4 Расчёт нормы невязки

Чтобы уменьшить время расчёта, выполнение критерия останова проверялось не после V-цикла, а после выполнения итераций предварительного сглаживания на самой точной сетке. Такой подход, во-первых, позволяет сэкономить на расчёте невязки, так как в случае невыполнения критерия останова её все равно пришлось бы считать, а, во-вторых, на времени выполнения V-цикла. Так как операции предварительного сглаживания на точной сетке производятся гораздо быстрее, чем весь V-цикл, если критерий останова достигается за счет сглаживания до начала огрубления, то нет необходимости выполнять последующие вычисления.

В качестве метрики для проверки критерия останова использовалась норма Гёльдера:

Особенностью реализации подсчёта нормы на GPU является редукция переменных из warp’ов, которые, вообще говоря, могут не выполняться одновременно.

Был использован алгоритм, заключающийся в следующей последовательности действий:

  1. Редукция внутри одного thread’а.

  2. Редукция внутри одного warp’а.

  3. Редукция внутри одного CUDA-блока.

  4. Редукция внутри всего CUDA-grid’а.

Начиная с архитектуры Kepler, для видеокарт NVidia стало возможным получение в CUDA-потоке данных, лежащих в регистрах соседних CUDA-потоков. Так как доступ к файлу регистров такой же быстрый, как доступ к L1-кэшу, данный подход в комбинации с методом сдваивания является наилучшим для редукции внутри одного warp’a.


real warpReduceMax(real val) {

//Цикл по всем thread'ам warp'а, осуществляющий сдваивание

for (int offset = warpSize / 2; offset > 0; offset /= 2){

//Чтение данных из регистра другого thread’а

real val2 = __shfl_down(val, offset);

val = (val > val2) ? val : val2;

}

return val;

}

На языке CUDA его можно описать так:

Данный подход не применим внутри всего CUDA-блока, т.к. чтение из соседних регистров возможно только внутри warp’a.

Для вычислений внутри блока в CUDA есть еще один тип памяти, работа с которой по производительности эквивалентна работе с L1 кэшем – shared (разделяемая память). Доступ к общей для нескольких thread’ов shared памяти возможен только внутри одного CUDA-блока, поэтому с ее использованием мы не можем произвести редукцию по всему grid’у, но можем выполнить ее внутри одного CUDA-block’a следующим образом:

  1. Выделяем внутри для каждого блока массив в shared-памяти, вмещающий по одному числу с плавающей запятой, для каждого warp’a. Внутри одного блока может быть не более 1024 потоков – значит для того, чтобы вместить информацию из всех warp’ов достаточно 1024/32 =32 ячеек массива.

  2. Нулевой поток каждого warp’а записывает результат внутренней редукции warp’а в отведенную для него ячейку.

  3. Происходит синхронизация потоков.

  4. Нулевой warp блока осуществляет редукцию указанного массива с помощью приведенного ранее алгоритма.

#typedef unsigned long long int uint64

__device__ double atomicMax(double * const address, const double value)

{

if (*address >= value)

{

return *address;

}

uint64 * const address_as_i = (uint64 *)address;

uint64 old = *address_as_i, assumed;

do

{

assumed = old;

if (__longlong_as_double(assumed) >= value)

{

break;

}

//выполняем сравнение и замену внутри атомарной операции

old = atomicCAS(address_as_i, assumed, __double_as_longlong(value));

} while (assumed != old);

return __longlong_as_double(assumed);

}

Теперь осталось произвести поблочную редукцию внутри grid’а. Здесь придётся воспользоваться глобальной памятью. В CUDA нет атомарной операции выбора максимального из двух чисел с плавающей запятой, но её можно реализовать с помощью атомарной операции сравнения и замены целых чисел, представив число с плавающей запятой, как целое: