У меня есть массив matrix со значениями 0, и я хочу увеличить некоторые из его элементов на 1. Индексы matrix, которые я хочу увеличить, хранятся в массиве indices. Мне нужно увеличить некоторые элементы несколько раз, поэтому я пытаюсь использовать массив мьютексов для каждого из элементов в matrix. Но когда я запускаю свой код, программа зависает, и я получаю тупик.
Я застрял с этой проблемой. Что я в конечном итоге хочу сделать, так это нарисовать непрерывный мазок кисти, который перекрывает себя с помощью CUDA, поэтому мне нужно получить доступ к одним и тем же пикселям изображения холста параллельно.
Вот мой код:
#include <iostream>
using namespace std;
__global__ void add_kernel(int* matrix, int* indices, int* d_semaphores, int nof_indices)
{
int index = threadIdx.x + blockIdx.x * blockDim.x; // thread id
int ind = indices[index]; // indices of target array A to increment
if (index < nof_indices) {
while (atomicCAS(&d_semaphores[ind], 0, 1) != 0);
matrix[ind] += 1;
atomicExch(&d_semaphores[ind], 0);
__syncthreads();
}
}
int main()
{
int nof_indices = 6; // length of an array B
int indices[6] = { 0,1,2,3,4,1 }; // array B; stores indices of an array A which to increment
int canvas[10]; // array A
int semaphores[10]; // mutex array with individual mutexes for each of array A elements
int* d_canvas;
int* d_indices;
int* d_semaphores;
memset(canvas, 0, sizeof(canvas)); // set all array A elements to 0
memset(semaphores, 0, sizeof(semaphores)); // set all array A elements to 0
cudaMalloc(&d_canvas, sizeof(canvas));
cudaMalloc(&d_semaphores, sizeof(semaphores));
cudaMalloc(&d_indices, sizeof(indices));
cudaMemcpy(d_canvas, &canvas, sizeof(canvas), cudaMemcpyHostToDevice);
cudaMemcpy(d_indices, &indices, sizeof(indices), cudaMemcpyHostToDevice);
cudaMemcpy(d_semaphores, &semaphores, sizeof(semaphores), cudaMemcpyHostToDevice);
add_kernel << <1, 6 >> > (d_canvas, d_indices, d_semaphores, nof_indices);
cudaMemcpy(&canvas, d_canvas, sizeof(canvas), cudaMemcpyDeviceToHost);
for (int it = 0; it < nof_indices; it++) {
cout << canvas[it] << endl;
}
cudaFree(d_canvas);
cudaFree(d_indices);
cudaFree(d_semaphores);
return 0;
}
В этом примере результирующий массив matrix должен выглядеть так: {1, 2 ,1 ,1,1,0}, но я получаю его только при запуске ядра с размерами << 6,1 >>.
Я использую CUDA 12.1, Geforce RTX 3060.
Спасибо
(Это работает только тогда, когда я устанавливаю поток на размер блока равным 1, но это не то, что я хочу)
Почему вы просто не делаете атомарные приращения соответствующих пикселей вместо использования мьютексов? Для справки, этот параллельный шаблон называется разбросом (с обработкой конфликтов).
@JérômeRichard Понятно, попробую версию на процессоре
@paleonix Мне нужен мьютекс, потому что мне также нужно будет отсортировать, в каком порядке увеличивать (в конце концов, я добавляю не только 1. Подумайте о рисовании непрозрачных мазков кисти, в зависимости от индекса вдоль мазка кисти, в дополнение к холсту может быть поверх текущего значения пикселя (где atomicAdd будет работать, вы правы) или «под» текущим значением пикселя (ничего не добавляя к текущему значению пикселя)





В модели исполнения pre-volta эта строка кода была бы проблематичной:
while (atomicCAS(&d_semaphores[ind], 0, 1) != 0);
Эта тема обычно рассматривается в этом блоге «Независимое планирование потоков», а также в различных вопросах SO, таких как этот и этот.
Однако, как указано в блоге (и в других источниках), модель исполнения volta должна допускать более гибкие парадигмы. Я считаю, что проблема здесь возникает из-за особенностиnvcc:
Чтобы облегчить миграцию при выполнении корректирующих действий, подробно описанных в независимом планировании потоков, разработчики Volta могут подписаться на планирование потоков Pascal с помощью комбинации параметров компилятора -arch=compute_60 -code=sm_70.
Если вы компилируете для архитектуры pre-volta, вы указываете компилятору, что вам нужна семантика pre-volta. Это может повлиять на поведение выполнения вашего кода, например, в случае, когда вы выполняете на вольте или более новой архитектуре, но компилируете для цели, предшествующей вольте.
Согласно моему тестированию, код блокируется sm_75, если я компилирую с использованием переключателей по умолчанию в CUDA 12.1, который по умолчанию выбирает цель sm_52 (включая PTX). Однако, если я компилирую для цели sm_75, код работает «нормально».
Я думаю, что ваш код не заблокируется на вашей RTX 3060, если вы скомпилируете для Volta или более новой цели. Если у вас нет причин не делать этого, общая рекомендация состоит в том, чтобы скомпилировать, указав цели, на которых вы хотите работать.
поменял на sm_75 все заработало! Большое спасибо
Архитектура, на которую я предлагаю ориентироваться, — это sm_86 для вашего графического процессора. Но любая вольта или более новая цель должна решить эту проблему.
@sergei Попробуйте пометить этот ответ как «принятый» (галочка под оценкой ответа), чтобы люди могли видеть, что ваша проблема решена.
Обратите внимание, что использование CAS и мьютексов на GPU очень неэффективно. Графические процессоры не предназначены для такой эффективной работы. Графические процессоры предназначены для выполнения совместных параллельных алгоритмов, а не алгоритмов с взаимным исключением (или последовательной атомарной операцией). Это связано с большим количеством параллелизма в сочетании с более высокой задержкой по сравнению с ЦП. Я настоятельно рекомендую пересмотреть свой алгоритм, чтобы не использовать это. Если вы не можете, рассмотрите возможность использования этого на процессоре. На самом деле, последовательное выполнение операции на ЦП может быть даже быстрее (из-за высокой частоты и ILP).