B. В. Монахов, И. Б. Керницкий, В. В. Спиридонов, Г. М. Седов, Е. А. Балашова, А. Е. Зыкова, С. В. Фомкин
МЕТОДИКА ИНТЕГРАЦИИ ВНЕШНИХ МАТЕМАТИЧЕСКИХ БИБЛИОТЕК С ЯЗЫКОМ JAVA*
Введение. Большинство известных математических библиотек, используемых для вычислений и обработки данных в физике, реализовано на языках Fortran (LAPACK, ARPACK и др.) или C++ (FFTW, ROOT и др.) либо поставляется в виде динамических библиотек (DLL или SO) (MKL, ACML, Sun PerfLib). Существуют также специальные реализации библиотек, использующие аппаратные ресурсы (например, NVidia CUDA BLAS) и напрямую не совместимые со стандартными аналогами.
Их непосредственное использование в языке Java сопряжено с большими сложностями и существенно понижает надёжность кода. Нами разработана методика интеграции внешних математических библиотек с программами на языке Java [1, 2], позволяющая решить указанные проблемы. В данной работе рассмотрены возможные способы оптимизации интерфейсной части (API) вызовов библиотечных функций, работы с памятью, а также использование нескольких библиотек со схожим API с возможностью выбора версии библиотеки.
Методика разделения программного кода. Программный код, используемый при подключении библиотеки, в рамках предложенной схемы разделяется на 3 уровня (рис. 1), на каждом из которых решаются задачи, специфические для соответствующего уровня.
Нижний уровень (ядро) - исходный код внешней математической библиотеки сохраняется без изменений (компилируется в DLL(SO) либо используется уже откомпилированный файл). Допускается использование разных ядер со схожим функционалом и интерфейсом (например, LAPACK, MKL, а также LAPACK с заменой BLAS на Go-ToBLAS или CUBLAS).
Средний уровень - библиотека DLL(SO), написанная на C+—+, являющаяся оболочкой над ядром. В случае с подключением к Java код среднего уровня можно разбить на 3 подуровня:
• адаптер - приводит интерфейс конкретного ядра к универсальному, что позволяет заменять одно ядро на другое без изменения остального кода;
• основной код оболочки - решает задачи оптимизации интерфейса, локального управления памятью, промежуточной обработки данных;
• код JNI решает задачи обмена данными с программой на Java, а также за сбор и передачу информации об ошибках в Java для порождение соответствующих исключений.
Верхний уровень - код, написанный на Java, использующий в своей работе вызовы native-функций JNI из библиотеки среднего уровня. Основной задачей при реализации методов верхнего уровня является предоставление удобного для специалистов предметной области интерфейса доступа к высокопроизводительной библиотеке. Для функции, имеющей большое количество параметров, можно легко реализовать несколько
• По материалам доклада на юбилейном семинаре «Вычислительная физика» 29—30 октября 2009 г., С.-Петербург.
© В. В. Монахов, И. Б. Керницкий, В. В. Спиридонов, Г. М. Седов, Е. А. Балашова, А. Е. Зыкова,
C. В. Фомкин, 2010
Рис. 1. Трёхуровневая модель подключения математических библиотек к Java
аналогов, в которых часть параметров задана по умолчанию, а неиспользуемые параметры опущены.
Допускается одновременное использование нескольких ядер. Предполагается использование некоторого стандартного ядра, обладающего полным набором стандартных функций, а также расширенных ядер, имеющих помимо стандартных ряд дополнительных функций. Например, LAPACK - стандартное ядро, MKL или CUBLAS + + LAPACK - расширенные. Возможно также использование дополнительных ядер, имеющих дополнительные функции, но не обладающих полным набором стандартных. Функции, отсутствующие в дополнительном ядре (CUBLAS), могут быть реализованы либо на уровне Java как вызовы из стандартного ядра через механизм наследования, либо, при необходимости, в реализации JNI или адаптере.
Применение изложенной выше схемы позволяет заменять или комбинировать ядра путём использования интерфейсов Java. Все классы, представляющие математические библиотеки со схожей функциональностью, унаследованы от общего (абстрактного или стандартного) класса (JLib) и реализуют один и тот же интерфейс (ILib).
Важными особенностями трехуровневой модели являются:
• сохранение надёжности и производительности исходных алгоритмов библиотеки;
• возможность заменять компоненты нижнего или среднего уровня на аналогичные (например, LAPACK на MKL или Sun PerfLib) без необходимости внесения изменений в код верхнего уровня или в программу на Java;
• возможность использовать код среднего уровня (без элемента JNI) при вызовах из других языков программирования;
• разделение сфер ответственности между разработчиками программного обеспечения разных уровней модели.
Программное обеспечение нижнего уровня является библиотечным и не подвергается изменениям. Программное обеспечение среднего уровня должны писать профессиональные программисты, при этом требуется минимальный объём знаний в предметной
Приложение Java ( Вызов Java методов J Библиотека на Java Интерфейс JNI Реализация native методов в DLL 'татическое связывание
Преобразование данных, работа с памятью, оптимизация интерфейса
области. Верхний уровень предполагается создавать с учётом требований научных задач с участием научных работников.
Управление данными. Отдельной задачей среднего уровня является оптимизация управления данными при работе с математической библиотекой. Использование объектов Java при проведении высокопроизводительных вычислений с использованием вещественных и комплексных векторов и матриц неэффективно по следующим причинам:
• формат хранения массивов в Java существенно отличается от используемого в математических библиотеках;
• в Java отсутствует встроенный класс комплексных чисел, а использование пользовательского класса для описания матриц нецелесообразно, так как приводит к крайне медленному выполнению операций и большому объёму использованной памяти;
• нет возможности освободить занимаемую память по команде пользователя.
Нами был разработан набор классов Java, реализующих через native-методы хранение вещественных или комплексных матриц в характерном для математических библиотек формате. Имеется возможность доступа к отдельным элементам, освобождения памяти по требованию, преобразования к стандартным объектам Java.
Такое решение позволяет передавать данные с верхнего уровня на средний наиболее оптимальным образом, а также оперировать данными без использования объектов Java для описания отдельных комплексных чисел в матрицах. Пользователь получает также возможность выполнять цепочки операций над матрицами без перевода их в массив Java. Например, в случае, если пользователю нужна только главная диагональ (несколько соседних диагоналей) результата, то матрицу-результат вообще необязательно целиком переводить в массив Java, что позволит сэкономить вычислительные ресурсы и память.
Кроме того, применение специальных классов для хранения данных позволяет прозрачно для пользователя задействовать аппаратные технологии с предварительной загрузкой данных в локальную память устройств (nVidia CUDA).
Практическое применение. Разработанная методика была использована для подключения функций оригинального пакета Netlib LAPACK и его реализаций MKL (Intel), ACML (AMD). Для матриц с 1000 элементами и более производительность при вызове из Java уменьшалась не более чем на 1-2 % по сравнению с оригинальной (с вызовами функций из библиотек без использования Java), а в ряде случаев даже превосходила оригинальную [2].
Для матриц, размер которых менее чем 16 х 16 элементов, время вызова алгоритма из Java сравнимо с временем вычислений. Поскольку размеры матриц в научных вычислениях обычно существенно превосходят данный порог, можно сделать вывод о применимости разработанной модели для интеграции математических библиотек с языком Java.
В рамках исследования описанной схемы было проведено сравнение скорости работы ряда алгоритмов из библиотек Netlib LAPACK 3.1, MKL 10.2.3, ACML 4.3.0. Вычисления проводились на компьютере с процессором Intel Q9550 (4 ядра, 2,83 ГГц, 12 МБ кэш памяти 2-го уровня, архитектура Core2) под управлением 64-битной версии ОС Microsoft Windows 7.
В случае Netlib LAPACK вычисления проводились в однопоточном режиме, так как библиотека не поддерживает параллельное выполнение. При использовании MKL
Рис. 2. Ускорение работы алгоритма решения систем линейных уравнений (ZGESV) по сравнению с Netlib LAPACK для различных математических библиотек
и ACML все вычисления проводились в 4 потока, таким образом аппаратные возможности платформы были задействованы полностью.
В [2] было показано, что для исследуемых алгоритмов графики зависимости времени вычислений от размера матрицы, построенные в двойном логарифмическом масштабе, хорошо аппроксимируются линейной зависимостью, что позволяет вычислить коэффициенты степенной регрессии вида: t(n) = b ■ na, где t - время вычислений, n - размер матрицы (число элементов равно n2), а - порядок алгоритма.
В таблице приведены коэффициенты регрессии для исследуемых алгоритмов LA-PACK. Можно отметить, что порядок алгоритмов в оптимизированных версиях библиотек, значительно ниже чем в стандартной Netlib LAPACK.
Аппроксимация зависимости времени вычисления (в секундах) от размера матрицы
Вещественные DGEEVX DGESV DGESVD
NetLib LAPACK 4,9 • 1СГ8 • n2’79 6,7 • 1CP10 • ??2,93 1,1 • 1СГ8 -??3’04
MKL l,3.1CT7.n2’6 7,3 • 1СГ9 • n2’24 3,6 • 1СГ8 • ??2’58
ACML 1,1 • 1СГ7 • И2’55 3,6 • 1СГ9 • n2’47 4,0 • 1CP8 • n2,1
Комплексные ZGEEVX ZGESV ZGESVD
NetLib LAPACK 2,2 • 1СГ7 -n2’71 1,9 • 1СГ9 -??2’92 2,5 • 1СГ8 • n3’01
MKL 4,7 • 1СГ7 • n2’46 7,7 • 1CP9 -n2’4 3,2 • 1СГ8 • ??2’69
ACML 3,7 • 1CP7 - и2’54 6,6 • 1СГ9 • ??2’58 4,5 • 1СГ8 • ??2’71
На рис. 2 приведены графики ускорения рассмотренных библиотек по отношению к аппроксимации скорости МеИіЬ ЬАРАСК для алгоритма решения систем линейных
уравнений с комплексными матрицами (ZGESV). В случае библиотек MKL и ACML видно, что коэффициент ускорения меняется в зависимости от количества точек, что в значительной степени обусловлено различием порядков методов. Наилучшие значения ускорения, как правило, получаются при количестве точек больше 500.
В случае библиотеки MKL приведены две зависимости - для стандартного ядра (MKL default) и для ядра, оптимизированного для процессоров с архитектурой Core2 (MKL optimized). Можно отметить, что скорость работы стандартного ядра MKL мало отличается от скорости работы библиотеки ACML, которая оптимизирована для платформы AMD. Использование в MKL оптимизированного ядра дает прирост в скорости до 3,5 раза по сравнению со стандартным.
Следует отметить, что вблизи некоторых размеров матриц скорость работы алгоритмов понижается. Например, в случае с MKL (ZGESV) выбор матрицы с размером, близким к 1024 точкам, приводит к снижению производительности на 15-20 % по сравнению со случаями матриц несколько больших или несколько меньших размеров. Вероятно, это связано с особенностями работы подсистемы памяти.
Использование в четырёхпоточном режиме библиотеки MKL с оптимизированным ядром дало следующий прирост скорости по сравнению с Netlib LAPACK:
• решение задачи на собственные значения (DGEEVX, ZGEEVX) - 2,5-3,5 раза;
• сингулярное разложение матриц (DGESVD, ZGESVD) - Т-9 раз;
• решение систем линейных уравнений (DGESV, ZGESV) - 9-10 раз для комплексных матриц, 14-15 раз в случае вещественных матриц.
Использование в четырёхпоточном режиме библиотеки ACML или библиотеки MKL со стандартным ядром дало следующий прирост скорости по сравнению с Netlib LAPACK:
• решение задачи на собственные значения (DGEEVX, ZGEEVX) - 2-2,5 раза;
• сингулярное разложение матриц (DGESVD, ZGESVD) - 5-6 раз;
• решение систем линейных уравнений (DGESV, ZGESV) - 3 раза для комплексных матриц, 5 раз в случае вещественных матриц.
Выводы. Предложенная методика интеграции библиотек в Java позволяет:
• использовать математические библиотеки в Java без потери производительности;
• писать высокопроизводительный код с помощью языка Java;
• заменять версии используемых библиотек (например, в зависимости от платформы), не изменяя исходный код на Java;
• использовать сразу несколько версий библиотек в одной программе на Java;
• эффективно оперировать в Java матрицами вещественных и комплексных чисел;
• реализовывать передачу данных во внешнюю память, например в память видеокарты, прозрачно для пользователя.
Литература
1. Монахов В. В., Керницкий И. Б., Евстигнеев Л. А. Трёхуровневая система разделения кода для разработки высокопроизводительных параллелизуемых программ // Мат. б междун. научн.-практ. семин. «Высокопроизводительные параллельные вычисления на кластерных системах». СПб., 2007. Т. 2. C. 57-65.
2. Керницкий И. Б., Спиридонов В. В., Монахов В. В. Подключение библиотек параллельных высокопроизводительных алгоритмов к программам на Java // Тр. 8 Междун. конф. HPC-2008. Казань, 2008. C. 43-47.
Статья поступила в редакцию 22 декабря 2009 г.