Pull to refresh

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

Reading time 8 min
Views 43K
Хотя технология 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:
Hubs:
+19
Comments 7
Comments Comments 7

Articles