Pull to refresh

OpenCL под C# это просто

High performance
Sandbox
Хотя технология OpenCL появилась ещё в 2008 году, большого распространения она не получила до сих пор. Плюсы технологии несомненны: ускорение вычислений, кроссплатформенность, способность исполнять код как под GPU, так и под CPU, поддержка стандарта целым рядом компаний: Apple, AMD, Intel, nVidia и некоторыми другими. Минусов не так много, но и они есть: более медленная работа на nVidia, чем через CUDA, сложность использования. Первый из минусов влияет только при серьёзной разработке, где скорость программы важнее кроссплатформенности. Второй и является основным препятствием на пути разработчиков, делающих выбор в пользу того или иного метода разработки. Чтобы разобраться в куче хэдэров, драйверов и стандартов требуется куча времени. Но не всё так плохо. Темой этой статьи будет короткий guide по тому, как наиболее простым способом можно запустить OpenCL под C# и получить удовольствие от параллельного программирования.

Настройка драйверов

Самой длительной и сложной частью является настройка драйверов для OpenCL. Скажу сразу, что если система стоит на компьютере больше года, у вас карточка AMD и драйвера в системе обновлялись — велика вероятность, что систему придётся переставлять.
AMD.
Хотя AMD и имеет некоторые проблемы с драйверами, у них замечательное SDK со множеством примеров, а форумы поддержки живые и оперативные. В последних версиях AMD Catalyst драйвера для OpenCL устанавливаются автоматически. SDK для работы можно взять здесь. Перед установкой проверьте, поддерживает ли ваша видеокарта OpenCL. Для AMD частичная поддержка начинается с 4300 серии, а полная с 5400.
nVidia.
В принципе, проблем с драйверами у nVidia меньше, но иногда они тоже встают. Поддержка OpenCL идёт примерно с 8600 серии. Взять драйвера можно тут.
Intel.
В отличие от AMD и nVidia проблем с драйверами от Intel у меня не возникло ни разу. Взять их можно тут.

Установка драйверов на систему ещё не гарантирует что они будут у вас работать. Количество багов у AMD зашкаливает (проблемы возникали при установке на 2 из 3х компьютеров), у nVidia оно велико (1 из 3х). Поэтому, после установки рекомендую сначала проверить, подключилось ли OpenCL. Наиболее просто это можно сделать через программы показывающие параметры видеокарт. Я пользуюсь GpuCapsViewer, так же работают opencl-z и GPU-Z.
Если проблемы возникли… Удалите все старые драйвера, переставьте. Для AMD — убедитесь что ставите правильную версию драйверов. Драйвера для ноутбуков у них часто глючные. Если проблемы не исчезли — переустановка винды вас спасёт.

Врапперы

Так как нашей целью является максимально простое программирование на OpenCL под C#, мы не будем заниматься извращениями и подключать OpenCL хэдэры, а воспользуемся уже готовыми библиотеками, упрощающими разработку. Самой полной и безглючной версией, как мне кажется, на сегодняшний день является cloo.dll, входящая в OpenTK. Самой простой в применении, автоматизирующей множество операций является OpenCLTemplate, являющаяся надстройкой над cloo. Из минусов последней — некоторые глюки при работе с AMD, например с последней версией драйверов (11.6) могут отказаться инициализироваться устройства. Так как проект OpenSource, глюки которые у меня были я нашёл и поправил, но когда зарелизят новую версию библиотеки — не знаю. Так же есть несколько менее известных врапперов, которые можно найти на просторах интернета.

Первая программа

В качестве первой программы посчитаем через OpenCL сумму двух векторов, v1 и v2. Пример программы написанной с использованием cloo.dll:

Программа на Cloo

private void button4_Click(object sender, EventArgs e)
{
//Установка параметров, инициализирующих видеокарты при работе. В Platforms[1] должен стоять индекс
//указывающий на используемую платформу
ComputeContextPropertyList Properties = new ComputeContextPropertyList(ComputePlatform.Platforms[1]);
ComputeContext Context = new ComputeContext(ComputeDeviceTypes.All, Properties, null, IntPtr.Zero);

//Текст програмы, исполняющейся на устройстве (GPU или CPU). Именно эта программа будет выполнять паралельные
//вычисления и будет складывать вектора. Программа написанна на языке, основанном на C99 специально под OpenCL.
string vecSum = @"
__kernel void
floatVectorSum(__global float * v1,
__global float * v2)
{
int i = get_global_id(0);
v1[i] = v1[i] + v2[i];
}

"
;
//Список устройств, для которых мы будем компилировать написанную в vecSum программу
List<ComputeDevice> Devs = new List<ComputeDevice>();
Devs.Add(ComputePlatform.Platforms[1].Devices[0]);
Devs.Add(ComputePlatform.Platforms[1].Devices[1]);
Devs.Add(ComputePlatform.Platforms[1].Devices[2]);
//Компиляция программы из vecSum
ComputeProgram prog = null;
try
{

prog = new ComputeProgram(Context, vecSum); prog.Build(Devs, "", null, IntPtr.Zero);
}

catch

{ }

//Инициализация новой программы
ComputeKernel kernelVecSum = prog.CreateKernel("floatVectorSum");

//Инициализация и присвоение векторов, которые мы будем складывать.
float[] v1 = new float[100], v2 = new float[100];
for (int i = 0; i < v1.Length; i++)
{
v1[i] = i;
v2[i] = 2 * i;
}
//Загрузка данных в указатели для дальнейшего использования.
ComputeBuffer<float> bufV1 = new ComputeBuffer<float>(Context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.UseHostPointer, v1);
ComputeBuffer<float> bufV2 = new ComputeBuffer<float>(Context, ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.UseHostPointer, v2);
//Объявляем какие данные будут использоваться в программе vecSum
kernelVecSum.SetMemoryArgument(0, bufV1);
kernelVecSum.SetMemoryArgument(1, bufV2);
//Создание програмной очереди. Не забудте указать устройство, на котором будет исполняться программа!
ComputeCommandQueue Queue = new ComputeCommandQueue(Context, Cloo.ComputePlatform.Platforms[1].Devices[0], Cloo.ComputeCommandQueueFlags.None);
//Старт. Execute запускает программу-ядро vecSum указанное колличество раз (v1.Length)
Queue.Execute(kernelVecSum, null, new long[] { v1.Length }, null, null);
//Считывание данных из памяти устройства.
float[] arrC = new float[100];
GCHandle arrCHandle = GCHandle.Alloc(arrC, GCHandleType.Pinned);
Queue.Read<float>(bufV1, true, 0, 100, arrCHandle.AddrOfPinnedObject(), null);
}


* This source code was highlighted with Source Code Highlighter.


А ниже та же программа, реализованная через OpenCLTemplate.DLL


private void btnOpenCL_Click(object sender, EventArgs e)
{
//Текст програмы, исполняющейся на устройстве (GPU или CPU). Именно эта программа будет выполнять паралельные
//вычисления и будет складывать вектора. Программа написанна на языке, основанном на C99 специально под OpenCL.
string vecSum = @"
__kernel void
floatVectorSum(__global float * v1,
__global float * v2)
{
int i = get_global_id(0);
v1[i] = v1[i] * v2[i];
}"
;

//Инициализация платформы. В скобках можно задавать параметры. По умолчанию инициализируются только GPU.
//OpenCLTemplate.CLCalc.InitCL(Cloo.ComputeDeviceTypes.All) позволяет инициализировать не только
//GPU но и CPU.
OpenCLTemplate.CLCalc.InitCL();
//Команда выдаёт список проинициализированных устройств.
List<Cloo.ComputeDevice> L = OpenCLTemplate.CLCalc.CLDevices;
//Команда устанавливает устройство с которым будет вестись работа
OpenCLTemplate.CLCalc.Program.DefaultCQ = 0;
//Компиляция программы vecSum
OpenCLTemplate.CLCalc.Program.Compile(new string[] { vecSum });
//Присовоение названия скомпилированной программе, её загрузка.
OpenCLTemplate.CLCalc.Program.Kernel VectorSum = new OpenCLTemplate.CLCalc.Program.Kernel("floatVectorSum");
int n = 100;

float[] v1 = new float[n], v2 = new float[n], v3 = new float[n];

//Инициализация и присвоение векторов, которые мы будем складывать.
for (int i = 0; i < n; i++)
{
v1[i] = i;
v2[i] = i*2;
}


//Загружаем вектора в память устройства
OpenCLTemplate.CLCalc.Program.Variable varV1 = new OpenCLTemplate.CLCalc.Program.Variable(v1);
OpenCLTemplate.CLCalc.Program.Variable varV2 = new OpenCLTemplate.CLCalc.Program.Variable(v2);

//Объявление того, кто из векторов кем является
OpenCLTemplate.CLCalc.Program.Variable[] args = new OpenCLTemplate.CLCalc.Program.Variable[] { varV1, varV2 };

//Сколько потоков будет запущенно
int[] workers = new int[1] { n };

//Исполняем ядро VectorSum с аргументами args и колличеством потоков workers
VectorSum.Execute(args, workers);

//выгружаем из памяти
varV1.ReadFromDeviceTo(v3);

}


* This source code was highlighted with Source Code Highlighter.

Как видно, второй вариант куда проще и интуитивнее.

Ссылки напоследок

В первую очередь программированию на OpenCL через С# посвящён этот сайтик — www.cmsoft.com.br. К сожалению, его ведёт мало народу, поэтому примеры часто неадекватные, а OpenCLTemplate, созданный авторами сайта весьма глючный.
Полезным местом является сайт www.opentk.com где весьма оперативно отвечают на вопросы о программирование через cloo.dll
Примерно те же ответы можно получить и на сайте sourceforge.net/projects/cloo
Стандарт программирования OpenCl, основанный на C99 описан здесь — www.khronos.org/opencl

Продолжение статьи "Введение в OpenCl" рассказывает об особенностях языка программирования, которым мы программируем видеокарту.
Tags:COpenClOpenClTemplateOpenTkCloowindows
Hubs: High performance
Total votes 27: ↑23 and ↓4+19
Views36K

Popular right now

Top of the last 24 hours