
- •3.3 Расширение языка c……………………………………………………….46
- •1.1 Задача Дирихле
- •1.2 Метод простых итераций
- •1.3 Методы Якоби
- •1.4 Методы Зейделя и его модификации
- •2 Параллельные методы решения задачи Дирихле для систем с общей памятью
- •2.1 Использование OpenMp для организации параллелизма
- •2.2 Проблема синхронизации параллельных вычислений
- •2.3 Возможность неоднозначности вычислений в параллельных программах
- •2.4 Исключение неоднозначности вычислений
- •2.5 Волновые схемы параллельных вычислений
- •3 Параллельные вычисления с использованием видеокарты
- •3.1 Общие сведения о gpu и nvidia cuda
- •3.1.1 Разница между cpu и gpu в параллельных расчётах
- •3.1.2 Первые попытки применения расчётов на gpu
- •3.1.3 Возможности nvidia cuda
- •3.1.4 Области применения параллельных расчётов на gpu
- •3.2 Аппаратная и программная реализация cuda
- •3.2.1 Состав nvidia cuda
- •3.2.2 Архитектура gpu
- •3.2.3 Модель программирования cuda: потоковая модель
- •3.2.4 Модель программирования cuda: модель памяти
- •3.3 Расширение языка c
- •3.3.1 Директива вызова ядра
- •3.3.2 Основы cuda host api
- •3.3.3 Работа с памятью в cuda
- •3.3.4 Использование событий для синхронизации на cpu
- •3.3.5 Получение информации об имеющихся gpu и их возможностях
- •3.3.6 Компиляция
- •Http://habrahabr.Ru/blogs/cuda/54707/
3.3 Расширение языка c
Перейдем непосредственно к рассмотрению тех добавлений, которые технология CUDA привнесла в язык С, и постараемся по возможности кратко и доступно рассказать о каждом из них.
Как уже упоминалось ранее, программы для CUDA (соответствующие файлы обычно имеют расширение .cu) пишутся на "расширенном" С/С++ и компилируются при помощи команды nvcc.
Сама технология CUDA вводит ряд дополнительных расширений для языка C, которые необходимы для написания кода для GPU:
Спецификаторы функций, которые показывают, как и откуда буду выполняться функции.
Спецификаторы переменных, которые служат для указания типа используемой памяти GPU.
Спецификаторы запуска ядра GPU.
Встроенные переменные для идентификации нитей, блоков и др. параметров при исполнении кода в ядре GPU .
Дополнительные типы переменных.
Теперь рассмотрим каждое из этих расширений более детально.
Как было сказано, спецификаторы функций определяют, как и откуда буду вызываться функции. Всего в CUDA три таких спецификатора:
Спецификатор |
Выполняется на |
Может вызываться из |
__device__ |
device |
device |
__global__ |
device |
host |
__host__ |
host |
host |
__host__ (по умолчанию)— функция, выполняемая на CPU, вызываемая с CPU (в принципе его можно и не указывать).
__global__ — функция, вызываемая с CPU и выполняемая потоками на GPU; означает ядро.
__device__ — функция, вызываемая (одним потоком) с GPU выполняется на GPU,
При этом спецификаторы __host__ и __device__ могут быть использованы вместе (это значит, что соответствующая функция может выполняться как на GPU, так и на CPU - соответствующий код для обеих платформ будет автоматически сгенерирован компилятором). В этом случае функции компилируются в двух видах.
При написании программы важно учитывать ограничения, накладывающиеся на функции, выполняемые на GPU, на __global__функции:
тип возвращаемого результата всегда void;
аргументы передаются через разделяемую/константную память;
не поддерживается переменное число входных аргументов;
не поддерживаются static-переменные внутри функции;
не поддерживается рекурсия;
указатели на __global__функции со стороны CPU поддерживаются;
на __device__функции:
не поддерживается переменное число входных аргументов;
не поддерживаются static-переменные внутри функции;
поддерживается рекурсия для устройств с вычислительными возможностями 2.0 и выше;
указатели на __ device __функции со стороны CPU поддерживаются для устройств с вычислительными возможностями 2.0 и выше, указатели со стоны CPU не поддерживаются;
встраиваются по умолчанию для устройств с вычислительными возможностями 1.х.
Для задания размещения в памяти GPU переменных используются следующие спецификаторы – __device__, __constant__ и __shared__.
__device__ объявляет переменную, размещаемую на устройстве в глобальном пространстве памяти. Время жизни этой переменной совпадает со временем жизни приложения. Доступ к ней может быть осуществлен из всех потоков на устройстве, а также с хоста через библиотеки времени выполнения.
__constant__ объявляет переменную, размещенную на устройстве в константной области памяти и доступную только для чтения.
__shared__ объявляет переменную, размещаемую в пространстве разделяемой памяти мультипроцессора, на котором исполняется блок потоков. Время жизни переменной совпадает со временем жизни блока потока. Доступ к ней может быть осуществлен из потока, принадлежащему блоку потоков.
На их использование также накладывается ряд ограничений:
эти спецификаторы не могут быть применены к полям структуры (struct или union);
соответствующие переменные могут использоваться только в пределах одного файла, их нельзя объявлять как extern;
запись в переменные типа __constant__ может осуществляться только CPU при помощи специальных функций;
__shared__ переменные не могут инициализироваться при объявлении
Так же стоит упомянуть о встроенных переменных, которые понадобятся при написании программы:
gridDim – переменная типа dim3, содержит размер гридa (решетки), выделенного при текущем вызове ядра.
blockDim – переменная типа dim3, содержит размер блока, выделенного при текущем вызове ядра.
blockIdx – переменная типа uint3, содержит индекс текущего блока внутри грида в вычислении на GPU.
threadIdx – переменная типа uint3, содержит индекс текущей нити в вычислении на GPU.
warpSize – переменная типа int, содержит размер варпа.
Помните, что данные переменные предназначены только для чтения. Следует заметить, что нет встроенной переменной для получения индекса потока среди всех потоков (т.е. всех потоков всех блоков). Но он может быть вычислен через значения других встроенных переменных. Если считать, что используется только х-компонента индексов, то искомый индекс idx находится по формуле
Idx=blockldx.x*blockDom.x+threadldx.x,
где blockldx.x*blockDom.x – смещение нулевого потока данного блока относительно нулевого потока нулевого блока, threadldx.x – смещение данного потока относительно нулевого потока данного блока.
Теперь поговорим о добавленных типах переменных. В язык вводятся одно- , двух- , трех- , четырех- мерные вектора из базовых типов – [u]char1[1..4], [u]short[1..4], [u]int[1..4], [u]long[1..4], float[1..4] и double2. Они являются структурами и обращение к компонентам вектора идет по именам - .x, .y, .z и .w. Для создания значений-векторов заданного типа служит конструкция вида make_<type name>.
Обратите внимание, что для этих типов не поддерживаются векторные покомпонентные операции, т.е. нельзя просто сложить два вектора при помощи оператора "+" - это необходимо явно делать для каждой компоненты.
Также для задания размерности служит тип dim3, основанный на типе uint3, но обладающий нормальным конструктором, инициализирующим все не заданные компоненты единицами, используется для задания числа блоков и потоков при вызове ядер.
Эти типы могут использоваться в хостовой (C/C++) части кода. Итак, мы рассмотрели расширения языка С и дали их краткую характеристику. Далее опишем наиболее важный набор функций, который поможет организовать совместную работу CPU и GPU. Более подробную информацию о предоставляемых CUDA возможностях можно найти в инструкции.