Блог

Полезные статьи и новости о жизни WaveAccess

Улучшение производительности R

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

Java, R, График зависимости, зависимость алгоритма от размера массива и реализации

Рисунок 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

Java, R, График зависимости, зависимость Времени Выполнения Алгоритма От Длины Массива И Реализации

Рисунок 3 – График зависимости времени выполнения алгоритма от длины массива и метода реализации

Выводы

Таким образом, при увеличении размеров входного массива реализация его сортировки и генерации перестановок на java будет значительно более эффективной, чем на языке R.
Этот метод позволяет заметно увеличить быстродействие алгоритмов, снизить время выполнения вычислений и количество необходимых системных ресурсов.

Если в текущем проекте у Вас есть задача оптимизировать вычисления, или же Вам требуется разработка нагруженного проекта, пишите нам на hello@wave-access.com

 

Ссылки

1. R performance

http://stat.ethz.ch/education/semesters/ss2015/Progr_R3/Performance.Rmd

2. Пакет rJava

https://www.rforge.net/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))

Заказать звонок

Удобное время:

Отменить

Пишите!

Присоединить
Файл не больше 30 Мб.
Отменить