Тупик CUDA AtomicCAS

У меня есть массив 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, но это не то, что я хочу)

Обратите внимание, что использование CAS и мьютексов на GPU очень неэффективно. Графические процессоры не предназначены для такой эффективной работы. Графические процессоры предназначены для выполнения совместных параллельных алгоритмов, а не алгоритмов с взаимным исключением (или последовательной атомарной операцией). Это связано с большим количеством параллелизма в сочетании с более высокой задержкой по сравнению с ЦП. Я настоятельно рекомендую пересмотреть свой алгоритм, чтобы не использовать это. Если вы не можете, рассмотрите возможность использования этого на процессоре. На самом деле, последовательное выполнение операции на ЦП может быть даже быстрее (из-за высокой частоты и ILP).

Jérôme Richard 13.05.2023 17:50

Почему вы просто не делаете атомарные приращения соответствующих пикселей вместо использования мьютексов? Для справки, этот параллельный шаблон называется разбросом (с обработкой конфликтов).

paleonix 13.05.2023 17:58

@JérômeRichard Понятно, попробую версию на процессоре

sergei 13.05.2023 18:52

@paleonix Мне нужен мьютекс, потому что мне также нужно будет отсортировать, в каком порядке увеличивать (в конце концов, я добавляю не только 1. Подумайте о рисовании непрозрачных мазков кисти, в зависимости от индекса вдоль мазка кисти, в дополнение к холсту может быть поверх текущего значения пикселя (где atomicAdd будет работать, вы правы) или «под» текущим значением пикселя (ничего не добавляя к текущему значению пикселя)

sergei 13.05.2023 18:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В модели исполнения 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 все заработало! Большое спасибо

sergei 13.05.2023 20:49

Архитектура, на которую я предлагаю ориентироваться, — это sm_86 для вашего графического процессора. Но любая вольта или более новая цель должна решить эту проблему.

Robert Crovella 13.05.2023 21:06

@sergei Попробуйте пометить этот ответ как «принятый» (галочка под оценкой ответа), чтобы люди могли видеть, что ваша проблема решена.

paleonix 18.05.2023 14:50

Другие вопросы по теме