Подключаем к Экселю GPU и ускоряем Эксель в 300 раз

Попалась мне задачка оптимизации, а так как я большой фанат Экселя, то и выбор инструмента был скорым. Единственная пакость: Эксель дико медленный. Так, на одну итерацию уходило как минимум 35 минут, а таких итераций планировалось сделать 1275 (как минимум)!

Цель этого небольшого проектика — ускорить исполнение VBA скриптов задействуя все доступные мне железяки: GPU и CPU. Ну и до кучи, так как библиотека моя, была реализована многозадачность.

Для тех, кто любит читать только код и не любит «растекания мыслию по древу», код находится здесь, инсталлятор здесь, архивированная библиотека и примеры (Excel/VBscript) здесь. Примеры также включены в инсталляцию (папка «demo»).

Предуведомление

Требования:

  • Excel (можно и без него, см. про ненормальное программирование в самом конце)

  • .Net framework v4.0.

  • Windows/System32/ должен содержать opencl.dll.

Структура

Внутренности библиотеки состоят из двух непересекающихся частей: конфигурации доступного оборудования (только информация, поменять там ничего нельзя) и, собственно, программирования найденных устройств.

Конфигурация

Конфигурация просто вываливает в табличку всю доступную информацию об устройствах, «до самой последней гнилушки». Логически OpenCl делит компьютер на платформы, внутри которых находятся устройства. Так, на моём домашнем компьютере обнаружилась только одна платформа с CPU на 4 процесса (Pentium 4417U 2.30GHz) и 12-ядерным GPU (Intel HD Graphics 610). А вот на компьютере жены обнаружилось целых 2 платформы: первая, с i3–7100U и 23-ядерной Intel HD Graphics 620, и вторая, опять же с i3–7100U и 5-ядерным GPU Hainan. Что интересно, интернеты говорят, что Intel HD Graphics 620 идёт с 24 ядрами. Либо OpenCl недосчитался, либо ядро отвалилось.

Программирование было реализовано на двух примерах: оценке производительности и параллельного исполнения кода.

Производительность

Производительность оценивалась двумя способами:

1. Умножением больших матриц.

2. Кодом, заимствованным с CodeProject («How to Use Your GPU in .NET»).

Время, затраченное на умножение двух матриц 2000 на 2000 (вычисления с двойной точностью) приведено на рисунке ниже. Обратите внимание на логарифмическую шкалу!

Время, затраченное на умножение двух матриц 2000 на 2000.Время, затраченное на умножение двух матриц 2000 на 2000.

Обозначения CPU и GPU соответствуют использованию OpenCl на CPU и GPU. Native CPU — случаю использования простейшей программы на C# для перемножения матриц. Ну, а VBA он и в Африке VBA. Кроме измерения времени результаты OpenCl вычислений сравнивались с результатами VBA (все сошлись!).

Довольно неожиданным открытием для меня стало то, что OpenCl на всех CPU считает в 8 раз быстрее, чем C# на одном процессоре. Так как у меня 2 процессора с 4 потоками, можно было бы предположить четырёхкратное уменьшение времени исполнения. Видимо здесь играет существенную роль то, что драйвер процессора, который компилирует CL-код, компилирует сразу в машинный код, а не использует интерпретатор, как C#.

Ну и результаты теста производительности полностью:

Время вычисления VBA:

3.1

минуты.

OpenCl на CPU в

73

раза быстрее VBA.

OpenCl на GPU в

327

раз быстрее VBA.

OpenCl на GPU в

4.5

раза быстрее CPU.

OpenCl на CPU в

8.3

раза быстрее C#, CPU.

C#, CPU в

8.9

раз быстрее VBA.

Следующим тестом производительности был тест, позаимствованный с CodeProject. Для моего домашнего Pentium 4417U (2×2.30 ГГц) выдал мне такие результаты производительности в Гфлопсах:

GFlops, single

GFlops, double

CPU

18.1

9.1

GPU

358.5

89.7

Асинхронность

Как, наверное, всем известно, Эксель до боли однозадачен. Чтобы запустить две задачи одновременно, надо приложить довольно много усилий. Всё описанное выше запускалось командой ExecuteSync. Тем не менее, кроме функции ExecuteSync имеются также и функции ExecuteAsync и ExecuteBackground. Отличие первой функции от второй в том, что ExecuteAsync использует callback, в то время как вторая возвращает информацию о завершении исполнения в свойстве ExecutionCompleted (True/False), которое надо время от времени опрашивать.

В принципе, первой была создана функция ExecuteAsync, но по мере тестирования было замечено, что с callback работать довольно сложно:

Эксель не любит и рушится:

Поэтому, в примере я использую ExecuteBackground. Результат работы в асинхронном режиме просто ошеломляет! Прогресс-бар бегает, процессы исполняются параллельно на CPU и GPU, поочерёдно завершаются, причём на один завершённый процесс CPU приходится 4 процесса GPU, как и предсказывали расчёты производительности умножения матриц.

Ну и обещанное ненормальное программирование (а всё написанное до того было нормальным программированием?).

Как оказалось, Эксель особо-то и не нужен! VBscript тоже поддерживает COM, правда со своими ограничениями: отсутствие типизации приводит к тому, что массивы необходимо передавать через object или ArrayList. Тем не менее, конфигурация работает как есть даже без дополнительных телодвижений:

Конфигурация устройств через VBscript.Конфигурация устройств через VBscript.

И, наконец, всё в одном месте:

GitHub

Инсталляция

Всё в архиве.

Примеры в папке «demo».

© Habrahabr.ru