Состояние гонки OpenCL с clSetKernelArg - PullRequest
0 голосов
/ 02 августа 2020
• 1000 -ентрантно, если одновременные вызовы работают с разными объектами cl_kernel. Однако поведение объекта cl_kernel не определено, если clSetKernelArg вызывается из нескольких потоков хоста в одном и том же объекте cl_kernel одновременно.

Мой вопрос: есть ли способ определить это поведение где ядра могут читать и писать из одного объекта ядра из нескольких потоков?

Я считал, что std :: atomi c на объекте, изменяемом ядрами, предотвратит это неопределенное поведение, но из того, что у меня есть попробовал, это приводит к тому, что вывод ядра дает неправильные значения. Есть ли лучший способ реализовать этот / известный метод работы с делом?

Это может быть полезно в случае, когда размер выделенного объекта настолько велик, что воссоздание нового объекта для каждого выполнения ядра тоже стоит много памяти, и предпочтительнее использовать разделяемый / переопределяемый объект.

1 Ответ

0 голосов
/ 03 августа 2020

где ядра могут читать и писать из одного объекта ядра из нескольких потоков?

Под «ядрами» вы подразумеваете фрагменты кода, выполняемые на GPU, а под «одним объектом ядра» вы имеете в виду cl_kernel в коде хоста? Ядра на GPU никогда не видят структуру cl_kernel , которая существует на стороне хоста. Я предполагаю, что вы говорите об использовании аргументов буферного объекта ( cl_mem ) ядрами.

Вы можете думать о cl_kernel как:

struct {
  size_t num_args;
  void* args[];
} _cl_kernel;
typedef struct _cl_kernel * cl_kernel;

Если вы вызываете clSetKernelArg (), он просто устанавливает что-то в этой структуре. Если вы вызываете clEnqueueNDRangeKernel (), он берет моментальный снимок структуры cl_kernel (аргументов) и добавляет его в некоторую внутреннюю очередь устройства. Под «снимком» я не имею в виду, что он создает скрытый снимок фактического содержимого буфера cl_mem ; он просто копирует ссылку на аргументы cl_mem . Поскольку это ссылка, на самом деле не имеет значения, используете ли вы один объект cl_kernel из нескольких потоков или вызываете clCreateKernel несколько раз с тем же именем, а затем используете эти cl_kernel в каждая нить; это просто вопрос удобства, конечный результат тот же.

Если у вас есть одна упорядоченная очередь команд, ваши ядра будут выполняться детерминированно, в порядке очереди. Если у вас есть несколько очередей команд (в порядке или вне очереди, не имеет значения), не будет никакого неявного упорядочивания между очередями , поэтому, если вы поставите в очередь одно и то же ядро ​​во все очереди, они будут выполняться в случайном порядке. Вы можете принудительно указать явный порядок с помощью событий. IOW, вы делаете:

cl_event event1, event2;
cl_kernel K;
...
clEnqueueNDRangeKernel(queue_1, K, ... , &event1);
clEnqueueNDRangeKernel(queue_2, K, ... , 1, &event1, &event2);

et c. Это заставит выполнение ядра ожидать на предыдущем, даже если они находятся в разных очередях. Но у вас будет только одно ядро, использующее буфер одновременно.

Если вы хотите использовать один и тот же буфер одновременно несколькими запущенными ядрами, тогда это зависит от модели использования этого буфера . Если вы выполняете только чтение, вы можете безопасно использовать буфер из любого количества ядер одновременно. Для использования записи, если вы знаете, что будете писать только в часть буфера, вы можете попробовать использовать суббуферы (clCreateSubBuffer). В противном случае вам, вероятно, не повезло (ну, может, вы могли бы попробовать atomi c ops, но это может сделать алгоритм непривычно медленным).

...