R — это универсальный язык программирования, разработанный для статистической обработки данных и работы с графикой, а также популярная программная среда вычислений с открытым исходным кодом. R занимает ведущие позиции в статистике, в анализе и добыче данных, широко используется в таких областях, как разведочный анализ данных, классические статистические тесты и высокоуровневая графика, и сегодня он фактически стал стандартом для статистических программ.
R зарекомендовал себя как действительно полезный инструмент в развивающейся области больших данных. Разработка на языке R, оптимизация, построение и внедрение R алгоритмов успешно используются в наукоемких проектах WaveAccess для обработки больших массивов данных.
Однако в вопросах производительности R показывает себя не лучшим образом, чему есть несколько причин:
1. Динамическая составляющая R
Почти все сущности языка могут быть изменены после их создания: определение, аргументы и область выполнения функций, добавление полей для объектов, изменение класса объекта после создания и т.п.
2. Сложность поиска значения, связанного с именем переменной
В R недостаточно только один раз произвести поиск значения для переменной, необходимо каждый раз начинать поиск с определенной метки и просматривать множество значений и областей выполнения.
3. Накладные расходы, связанные с отложенными вычислениями аргументов функций
Для реализаций ленивых вычислений для каждой функции создается дополнительный promise-объект, который содержит данные, необходимые для вычисления результата функции. На создание такого объекта расходуется память и время, что также сказывается на скорости выполнения.
Для ускорения работы R с большими объемами данных мы используем пакет rJava [1], который предоставляет низкоуровневый интерфейс для создания и дальнейшего доступа к java-объектам и методам.
Запуск java методов в R
1. Допустим, у нас есть класс на java, предоставляющий метод сортировки простых числовых массивов.
import java.util.Arrays; public class JavaSamples { public static int[] sort(int[] array) { Arrays.sort(array); return array; } }
2. Скомпилируем исходный файл класса. В рабочей папке (в нашем случае "D:/R optimizations/") должен появиться файл JavaSamples.class.
> javac JavaSamples.java
! Необходимо так же проверить, настроены ли переменные окружения для доступа к java, javac и R.
3. После запуска R установим пакет rJava, если он не был установлен ранее, и загрузим библиотеку для дальнейшего использования. Далее работаем в R консоли.
>> install.packages("rJava") >> require(rJava)
4. Затем установим рабочую директорию в текущую рабочую папку (в которой находится файл JavaSamples.class).
>>> setwd("D:/R optimizations/")
5. Инициализируем JVM в рабочей директории.
>> .jinit('.') >> #.jaddClassPath("D:/directory/to/jars/")
! Если необходимо подключить дополнительные jar файлы, используем команду jaddClassPath.
Теперь у нас есть доступ к нашему классу JavaSamples. Создаем объект класса, вызываем метод sort и передаем параметром вектор чисел. Для преобразования числового вектора R к ссылке на массив целых чисел Java используется метод .jarray.
>> javaObj <- .jnew("JavaSamples") >> result <- .jcall(javaObj, "[I", "sort", .jarray(array))
! Необходимо обратить внимание на определение типов для результата, возвращаемого java-функцией. В нашем случае массив целых чисел мы обозначим как "[I" (описание типов возвращаемых данных – таблица 1).
6. Получаем объект result – отсортированный массив.
Таблица 1. Описание типов происходит в нотации JNI (Java Native Interface).
Аббревиатура |
Тип |
"V" |
void |
"I" |
integer |
"D" |
double (numeric) |
"J" |
long |
"F" |
float |
"Z" |
boolean |
"C" |
char (integer) |
"S" |
String |
"B" |
byte (raw) |
"L<class>" |
Java-объект указанного класса (например, "Ljava/lang/Object") |
"[<type>" |
Массив объектов указанного типа (например, "[D" – массив типа double) |
Сборка и запуск java пакета
Если мы хотим поддержать наш метод сортировки в R пакете, нам необходимо добавить несколько шагов в обычный процесс сборки R пакета.
Структура папки для создания пакета javaSortPack приведена на рисунке 1. На рисунке выделены цветом элементы, которые необходимо добавить для поддержки java в обычную структуру R пакета.
javaSortPack
- inst
- java
- javaSamples.jar
- java
- JavaSamples.java
- man
- JavaSamples.Rd
- R
- javaSamples.R
- onLoad.R
- DESCRIPTION
- NAMESPACE
Рисунок 1 – Структура папки для создания пакета javaSortPack
1. В папку inst/java/ добавим Java-архив javaSamples.jar, который содержит единственный файл JavaSamples.class. В эту же папку стоит добавить дополнительные java библиотеки, если они потребуются.
> jar cvf program.jar -C path/to/classes .
! Для создания Java архива, используем команду jar [3].
2. Исходные файлы java можно добавить либо в jar, либо - в папку java/ на первом уровне пакета javaSortPack.
3. Для автоматического запуска JVM и добавления содержимого папки java/ в class path (путь поиска классов и библиотек для загрузки JVM), добавим в папку R/ файл onLoad.R. Функция .onLoad будет автоматически вызываться каждый раз при загрузке R пакета.
.onLoad >- function(libname, pkgname) { #options(java.parameters = "-Xmx2000m") .jpackage(pkgname, lib.loc = libname) }
! Для увеличения памяти, выделяемой для JVM, в функцию .onLoad можно добавить опцию options.
4. Следующий файл в папке R/ javaSamples.R содержит функцию javaSort, которая и будет вызывать java-код. Описание запуска функции не изменяется относительно запуска без пакета (см. предыдущую часть описания).
javaSort <- function(array) { # создаем java-объект класса JavaSamples javaObj <- .jnew("JavaSamples") # вызываем java-функцию sort, передаем параметром функции массив array # возвращаемый результат – массив целых чисел result <- .jcall(javaObj, "[I", "sort", .jarray(array) ) return (result) }
5. Далее собираем R пакет обычным способом [4].
Тестирование
Сортировка массива
На простом примере сортировки массива посмотрим различие в производительности стандартной функции R sort и сортировки реализованной на java. Скрипт для сравнения скорости выполнения сортировки:
sort_iterations = 10 r_sort <- function(array) { result <- sort(array) return (result) } java_sort <- function(array) { javaObj <- .jnew("JavaSamples") result <- .jcall(javaObj, "[I", "sort", .jarray(array) ) return (result) } length = 2000 * 10 array <- sample.int(length, replace = TRUE) print_time(for (i in 1:sort_iterations) { r_sort(array) }) print_time(for (i in 1:sort_iterations) { java_sort(array) })
Зависимость скорости выполнения от размера массива приведена в таблице 2 и рисунке 2.
Таблица 2 - Зависимость времени выполнения сортировки от размера массива и метода сортировки (количество итераций = 10)
Размер массива |
R сортировка, сек |
Java сортировка, сек |
2000 * 10 |
0,02 |
0,02 |
2e+05 |
0,17 |
0,14 |
2e+06 |
2,67 |
1,53 |
2e+07 |
27,05 |
17,61 |
Рисунок 2 – График зависимости времени выполнения сортировки от размера массива и типа сортировки
Перестановки
Сравним время выполнения алгоритма, генерирующего перестановки массива. Код java-класса и R скрипта приведены в приложении. Зависимость времени выполнения от реализации, количества элементов в массиве приведена в таблице 3 и на рисунке 3.
Таблица 3 - Зависимость времени выполнения алгоритма от размера массива и метода реализации
Длина массива |
8 |
9 |
10 |
11 |
12 |
13 |
R |
1,27 |
11,41 |
115,11 |
1274,12 |
||
Java |
0 |
0 |
0,04 |
0,47 |
4,97 |
67,58 |
Рисунок 3 – График зависимости времени выполнения алгоритма от длины массива и метода реализации
Выводы
Таким образом, при увеличении размеров входного массива реализация его сортировки и генерации перестановок на java будет значительно более эффективной, чем на языке R.
Этот метод позволяет заметно увеличить быстродействие алгоритмов, снизить время выполнения вычислений и количество необходимых системных ресурсов.
Если в текущем проекте у Вас есть задача оптимизировать вычисления, или же Вам требуется разработка нагруженного проекта, пишите нам на hello@wave-access.com
Ссылки
1. R performance
http://stat.ethz.ch/education/semesters/ss2015/Progr_R3/Performance.Rmd
2. Пакет rJava
3. Описание параметров команды
https://docs.oracle.com/javase/tutorial/deployment/jar/build.html
4. Создание расширений для R, сборка R пакетов
http://cran.r-project.org/doc/manuals/r-release/R-exts.html
5. Подробный пример запуска java кода из R-пакета
ftp://cran.r-project.org/pub/R/web/packages/helloJavaWorld/vignettes/helloJavaWorld.pdf
Приложение
JavaSamples.java
import java.util.Arrays; public class JavaSamples { public static int[] sort(int[] array) { Arrays.sort(array); return array; } public static int javaPermutateArray(int[] array) { return gen(array, 0, array.length); } private static int gen(int[] array, int cur, int length) { int result = 0; if (cur == length) { result = 1; } else { for (int index = cur; index < length; ++index) { swap(array, index, cur); result += gen(array, cur + 1, length); swap(array, index, cur); } } return result; } private static void swap(int[] array, int i, int j) { if (i != j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } }
permutationArray.R
# Path to this file setwd('D:/R optimizations') require(rJava) .jinit('.') swap <- function(array, i, j) { if (i != j) { tmp <- array[i] array[i] <- array[j] array[j] <- tmp } return (array) } gen <- function(array, cur, len) { result <- 0 if (cur > len) { result = 1; } else { for (index in cur:len) { array <- swap(array, index, cur) result <- result + gen(array, cur + 1, len) array <- swap(array, index, cur) } } return (result) } r_permutate_array <- function(array) { result <- gen(array, 1, length(array)) return(result) } java_permutate_array <- function(array) { javaObj <- .jnew("JavaSamples") result <- .jcall(javaObj, "I", "javaPermutateArray", .jarray(array)) return (result) } print_time <- function(x) { print(system.time(x)) } length = 7 array <- 1:length print_time(r.result <- r_permutate_array(array)) print_time(java.result <- java_permutate_array(array))