Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
отчет по практике Баулина.doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
1.98 Mб
Скачать

3.3.1 Директива вызова ядра

Опишем основную директиву запуска ядра, напомним, что ядро – это исполняемая функция. По умолчанию вызов ядра является асинхронным. Спецификаторы запуска ядра служат для описания количества блоков, нитей и памяти, которые вы хотите выделить при расчете на GPU. Синтаксис запуска ядра имеет следующий вид: kernelName <<<Dg,Db,Ns,S>>> ( args )

Здесь kernelName это имя (адрес) соответствующей __global__ функции, Dg - переменная (или значение) типа dim3, задающая размерность и размер grid'a (сетки в блоках), Db - переменная (или значение) типа dim3, задающая размерность и размер каждого блока (в нитях), Ns - переменная (или значение) типа size_t, задающая дополнительный объем shared-памяти, которая должна быть динамически выделена (к уже статически выделенной shared-памяти), S - переменная (или значение) типа cudaStream_t задает поток (CUDA stream), в котором должен произойти вызов, по умолчанию используется поток 0. Через args обозначены аргументы вызова функции kernelName.

Также в язык С добавлена функция void __syncthreads(), осуществляющая, барьерную синхронизацию всех нитей одного блока. Управление из нее будет возвращено только тогда, когда все нити данного блока вызовут эту функцию. Т.е. когда весь код, идущий перед этим вызовом, уже выполнен (и, значит, на его результаты можно смело рассчитывать). Эта функция очень удобная для организации бесконфликтной работы с shared-памятью. cudaThreadSynhronize() – также является барьерной синхронизацией для всех вызовов функций на устройстве (вызывается хостом).

Кроме того CUDA поддерживает все математические функции из стандартной библиотеки С, однако с точки зрения быстродействия лучше использовать их float-аналоги (а не double) - например sinf. Кроме этого CUDA предоставляет дополнительный набор математических функций (__sinf, __powf и т.д.) обеспечивающие более низкую точность, но заметно более высокое быстродействие чем sinf, powf и т.п.

3.3.2 Основы cuda host api

Еще раз напомним, что CUDA API для CPU (host) выступает в двух формах – низкоуровневый CUDA drievr API и CUDA runtime API (реализованный через CUDA driver API). В своем приложении можно использовать только один из них. Далее будем рассматривать CUDA runtime API, как более простой и удобный.

Все функции CUDA driver API начинаются с префикса cu, а все функции CUDA runtime API начинаются с префикса cuda. Каждый из этих API предоставляет основной набор базовых функций, таких как перебор всех доступных устройств (GPU), работа с контекстами и потоками, работа с памятью GPU, взаимодействие с OpenGL и D3D.

CUDA runtime API не требует явной инициализации - она происходит автоматически при первом вызове какой-либо его функции.

Для управления устройствами CUDA runtime API использует следующие функции:

cuda Error_t cudaGetDeviceCount(int* count) – возвращает число доступных устройств;

cuda Error_t cudaGetDevice(int* dev) – возвращает используемое устройство;

cuda Error_t cudaGetDeviceProperties(struct cudeDeviceProp* prop, int dev) – возвращает структуру, содержащую свойства устройства;

cuda Error_t cudaSetDevice(int dev) – устанавливает устройство с заданным номером;

cuda Error_t cudaChooseDevice(int* dev, const struct cudeDeviceProp* prop) – выбирает устройство, в наибольшей степени соответствующее конфигурации.

Обратим внимание на важный момент работы с CUDA: многие функции API являются асинхронными, т.е. управление возвращается еще до реального завершения требуемой операции.

К числу асинхронных операций относятся:

  • запуск ядра

  • функции копирования памяти, имена которых оканчиваются на Async

  • функции копирования памяти device <-> device

  • функции инициализации памяти.

CUDA поддерживает синхронизацию через потоки (streams) - каждый поток задает последовательность операций, выполняемых в строго определенном порядке. При этом порядок выполнения операций между разными потоками не является строго определенной и может изменяться.

Каждая функция CUDA API (кроме запуска ядра) возвращает значение типа cudaError_t. При успешном выполнении функции возвращается cudaSuccess, в противном случае возвращается код ошибки.

Получить описание ошибки в виде строки по ее коду можно при помощи функции cudaGetErrorString (cudaError_t code);

Также можно получить код последней ошибки при помощи функции cudaError_t cudaGetLastError();

Но в силу асинхронности выполнения многих вызовов, для получения кода ошибки лучше использовать функцию cudaError_t cudaThreadSynchronize, которая дожидается завершения выполнения на GPU всех переданных запросов и возвращает ошибку, если один из этих запросов привел к ошибке.