R:Оптимизация/Компиляция в байт-код — различия между версиями

Материал Psylab.info - энциклопедии психодиагностики
Перейти к: навигация, поиск
м (Примечания)
м (Загрузка 2881-compilation.wiki)
 
(не показано 7 промежуточных версий этого же участника)
Строка 1: Строка 1:
 +
<!-- R:Оптимизация/Компиляция в байт-код -->
 +
 +
 +
 
{{CC-BY-4.0|author=автором Артём Клевцов}}
 
{{CC-BY-4.0|author=автором Артём Клевцов}}
  
 
{{Pkg-req-notice}}
 
{{Pkg-req-notice}}
  
В данном материале рассматривается один из способов ускорения выполнения кода путём компиляции в [http://ru.wikipedia.org/wiki/Байт-код байт-код]. Байт-код - машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.
+
В данном материале рассматривается один из способов ускорения выполнения кода путём компиляции выражений, функций и скриптов R в [http://ru.wikipedia.org/wiki/Байт-код байт-код]. Байт-код - машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.
  
 
Программа на байт-коде обычно выполняется интерпретатором байт-кода (обычно он называется виртуальной машиной, поскольку подобен компьютеру). Преимущество - в портируемости, т. е. один и тот же байт-код может исполняться на разных платформах и архитектурах. То же самое преимущество дают интерпретируемые языки. Однако, поскольку байт-код обычно менее абстрактный, более компактный и более «компьютерный», чем исходный код, эффективность байт-кода обычно выше, чем чистая интерпретация исходного кода, предназначенного для правки человеком.
 
Программа на байт-коде обычно выполняется интерпретатором байт-кода (обычно он называется виртуальной машиной, поскольку подобен компьютеру). Преимущество - в портируемости, т. е. один и тот же байт-код может исполняться на разных платформах и архитектурах. То же самое преимущество дают интерпретируемые языки. Однако, поскольку байт-код обычно менее абстрактный, более компактный и более «компьютерный», чем исходный код, эффективность байт-кода обычно выше, чем чистая интерпретация исходного кода, предназначенного для правки человеком.
Строка 15: Строка 19:
 
Рассмотрим эти опции:
 
Рассмотрим эти опции:
  
* <code>optimize</code> --- определяет уровень оптимизации: принимает значения от 0 до 3 (по умолчанию 2);
+
* <code>optimize</code> - определяет уровень оптимизации: принимает значения от 0 до 3 (по умолчанию 2);
* <code>suppressAll</code> --- управляет сообщениями: принимает значения <code>TRUE</code> или <code>FALSE</code> (по умолчанию code>FALSE</code>);
+
* <code>suppressAll</code> - управляет сообщениями: принимает значения <code>TRUE</code> или <code>FALSE</code> (по умолчанию code>FALSE</code>);
* <code>suppressUndefined</code> --- управление сообщения о неопределённых (undefined) переменных: может принимать значения <code>TRUE</code> или список имён переменных (по умолчанию ".Generic", .Method", ".Random.seed", ".self").
+
* <code>suppressUndefined</code> - управление сообщения о неопределённых (undefined) переменных: может принимать значения <code>TRUE</code> или список имён переменных (по умолчанию ".Generic", .Method", ".Random.seed", ".self").
  
 
Получить текущее значение глобальных опций компиляции можно с помощью функции <code>getCompilerOption()</code>:
 
Получить текущее значение глобальных опций компиляции можно с помощью функции <code>getCompilerOption()</code>:
  
{{r-code|code=<nowiki>> getCompilerOption("optimize")
+
{{r-code|code=<nowiki>getCompilerOption("optimize")
[1] 2
+
#> [1] 2
> getCompilerOption("suppressAll")
+
getCompilerOption("suppressAll")
[1] FALSE
+
#> [1] FALSE
> getCompilerOption("suppressUndefined")
+
getCompilerOption("suppressUndefined")
[1] ".Generic"    ".Method"      ".Random.seed" ".self"</nowiki>}}
+
#> [1] ".Generic"    ".Method"      ".Random.seed" ".self"
 +
</nowiki>}}
  
 
== Компиляция функций и выражений ==
 
== Компиляция функций и выражений ==
 +
 +
Скомпилировать отдельно взятое выражение можно с помощью функции <code>compile()</code>.
 +
 +
{{r-code|code=<nowiki># объявляем переменные
 +
s <- as.double(0)
 +
x <- as.double(1:1000)
 +
# объявляем выражения
 +
expr <- expression(for (i in x) s <- s + i)
 +
exprc <- compile(for (i in x) s <- s + i)
 +
# сравниваем результат работы выражений
 +
identical(eval(expr), eval(exprc))
 +
#> [1] TRUE
 +
# сравниваем производительность выражений
 +
microbenchmark(eval(expr), eval(exprc))
 +
#> Unit: nanoseconds
 +
#>        expr    min    lq median    uq    max neval
 +
#>  eval(expr) 191031 200750 205432 210436 291039  100
 +
#>  eval(exprc)    924  1202  1346  1628  11845  100
 +
</nowiki>}}
  
 
Компиляция функций осуществляется с помощью функции <code>cmpfun()</code>. Возьмём пример из документации к функции <code>cmpfun()</code>, вкотором предлагается сравнить производительность различных реализаций функции <code>lapply()</code>, а также их скомпилированных версий:
 
Компиляция функций осуществляется с помощью функции <code>cmpfun()</code>. Возьмём пример из документации к функции <code>cmpfun()</code>, вкотором предлагается сравнить производительность различных реализаций функции <code>lapply()</code>, а также их скомпилированных версий:
Строка 40: Строка 64:
 
     for(i in seq(along = X))
 
     for(i in seq(along = X))
 
         rval[i] <- list(FUN(X[[i]], ...))
 
         rval[i] <- list(FUN(X[[i]], ...))
     names(rval) <- names(X)   # keep `names' !
+
     names(rval) <- names(X)               # keep `names' !
 
     return(rval)
 
     return(rval)
 
}
 
}
Строка 56: Строка 80:
 
             rval[[i]] <- v
 
             rval[[i]] <- v
 
     }
 
     }
     names(rval) <- names(X)   # keep `names' !
+
     names(rval) <- names(X)               # keep `names' !
 
     return(rval)
 
     return(rval)
}</nowiki>}}
+
}
 +
</nowiki>}}
  
 
Скомпилируем эти функции в бат-код:
 
Скомпилируем эти функции в бат-код:
  
{{r-code|code=<nowiki>> la1c <- cmpfun(la1)
+
{{r-code|code=<nowiki>la1c <- cmpfun(la1)
> la2c <- cmpfun(la2)
+
la2c <- cmpfun(la2)
> lapplyc <- cmpfun(lapply)</nowiki>}}
+
lapplyc <- cmpfun(lapply)
 +
</nowiki>}}
  
 
Сравним производительность этих функций:
 
Сравним производительность этих функций:
  
{{r-code|code=<nowiki>> microbenchmark(lapply(x, is.null), la1(x, is.null), la2(x, is.null),
+
{{r-code|code=<nowiki>x <- 1:1000
+               lapplyc(x, is.null), la1c(x, is.null), la2c(x, is.null))
+
microbenchmark(lapply(x, is.null), la1(x, is.null), la2(x, is.null),
Unit: microseconds
+
               lapplyc(x, is.null), la1c(x, is.null), la2c(x, is.null))
                 expr  min   lq median    uq max neval
+
#> Unit: microseconds
   lapply(x, is.null) 174.1 181.2  196.4  229.5 946   100
+
#>                 expr  min     lq median    uq   max neval
     la1(x, is.null) 786.0 816.2 838.6 985.6 1812   100
+
#>   lapply(x, is.null) 173.1 183.2  193.4  205.2 294.4   100
     la2(x, is.null) 967.6 996.9 1085.8 1359.9 2417   100
+
#>     la1(x, is.null) 792.3  836.1 861.1 929.9 1807.6  100
  lapplyc(x, is.null) 173.4 181.1  196.8 214.7  333   100
+
#>     la2(x, is.null) 965.0 1012.1 1036.6 1085.9 2769.5   100
     la1c(x, is.null) 392.0 409.8 433.8 502.3 1400   100
+
#> lapplyc(x, is.null) 171.5  182.4 189.1  200.3 265.6   100
     la2c(x, is.null) 403.6 415.6 439.7 532.1 1361   100</nowiki>}}
+
#>     la1c(x, is.null) 394.2  416.0 428.0 446.9 1217.8   100
 +
#>     la2c(x, is.null) 397.6 424.5 439.1 457.2 1216.0   100
 +
</nowiki>}}
  
 
Обращается на себя внимание, что скомпилированная версия функции <code>lapply()</code> не превосходит по производительности оригинальную версию. Происходит это потому, что функция <code>lapply()</code> использует написанную на C, скомпилированную функцию. Также отметим, что скомпилированные версии функций <code>la1()</code> и <code>la2()</code> показываются практически одинаковую производительность, тогда как нескомпилированные версии довольно сильно различались. Это достигается за счёт высокой оптимизации работы циклов при компиляции функций.
 
Обращается на себя внимание, что скомпилированная версия функции <code>lapply()</code> не превосходит по производительности оригинальную версию. Происходит это потому, что функция <code>lapply()</code> использует написанную на C, скомпилированную функцию. Также отметим, что скомпилированные версии функций <code>la1()</code> и <code>la2()</code> показываются практически одинаковую производительность, тогда как нескомпилированные версии довольно сильно различались. Это достигается за счёт высокой оптимизации работы циклов при компиляции функций.
Строка 85: Строка 113:
 
Ещё одна возможность, предоставляемая пакетом {{r-package|compiler|core=true}} - это компиляция R-скриптов. Создание скомпилированных файлов осуществляется с помощью функции \texttt{cmpfile()}, а загрузка скомпилированных файлов с помощью функции <code>loadcmp()</code>. Если выходной файл не указан, то выходной файл имеет тоже имя, что и входной, но с расширением <code>.Rc</code>. Пример использования:
 
Ещё одна возможность, предоставляемая пакетом {{r-package|compiler|core=true}} - это компиляция R-скриптов. Создание скомпилированных файлов осуществляется с помощью функции \texttt{cmpfile()}, а загрузка скомпилированных файлов с помощью функции <code>loadcmp()</code>. Если выходной файл не указан, то выходной файл имеет тоже имя, что и входной, но с расширением <code>.Rc</code>. Пример использования:
  
{{r-code|code=<nowiki>> cmpfile("script.R", "script.Rc")
+
{{r-code|code=<nowiki>cmpfile("script.R", "script.Rc")
> loadcmp("script.Rc")</nowiki>}}
+
loadcmp("script.Rc")
 +
</nowiki>}}
  
 
== JIT-компиляция ==
 
== JIT-компиляция ==
Строка 101: Строка 130:
  
 
<references />
 
<references />
 +
 +
== Ссылки ==
 +
 +
* [http://homepage.stat.uiowa.edu/~luke/R/compiler/compiler.pdf A Byte Code Compiler for R]
  
 
[[Категория:R]]
 
[[Категория:R]]
 
[[Категория:Оптимизация кода]]
 
[[Категория:Оптимизация кода]]

Текущая версия на 17:45, 28 мая 2014




В данном материале рассматривается один из способов ускорения выполнения кода путём компиляции выражений, функций и скриптов R в байт-код. Байт-код - машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.

Программа на байт-коде обычно выполняется интерпретатором байт-кода (обычно он называется виртуальной машиной, поскольку подобен компьютеру). Преимущество - в портируемости, т. е. один и тот же байт-код может исполняться на разных платформах и архитектурах. То же самое преимущество дают интерпретируемые языки. Однако, поскольку байт-код обычно менее абстрактный, более компактный и более «компьютерный», чем исходный код, эффективность байт-кода обычно выше, чем чистая интерпретация исходного кода, предназначенного для правки человеком.

В составе базовых пакетов R поставляется пакет compiler, который входит состав ядра R, но не загружается по умолчанию при старте R-сессии. Данный пакет включает в себя ряд функций для компиляции R-кода в байт-код.

Параметры компиляции

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

Рассмотрим эти опции:

  • optimize - определяет уровень оптимизации: принимает значения от 0 до 3 (по умолчанию 2);
  • suppressAll - управляет сообщениями: принимает значения TRUE или FALSE (по умолчанию code>FALSE</code>);
  • suppressUndefined - управление сообщения о неопределённых (undefined) переменных: может принимать значения TRUE или список имён переменных (по умолчанию ".Generic", .Method", ".Random.seed", ".self").

Получить текущее значение глобальных опций компиляции можно с помощью функции getCompilerOption():

КодR

<syntaxhighlight lang="r">getCompilerOption("optimize") #> [1] 2 getCompilerOption("suppressAll") #> [1] FALSE getCompilerOption("suppressUndefined") #> [1] ".Generic" ".Method" ".Random.seed" ".self" </syntaxhighlight>

Компиляция функций и выражений

Скомпилировать отдельно взятое выражение можно с помощью функции compile().

КодR

<syntaxhighlight lang="r"># объявляем переменные s <- as.double(0) x <- as.double(1:1000) # объявляем выражения expr <- expression(for (i in x) s <- s + i) exprc <- compile(for (i in x) s <- s + i) # сравниваем результат работы выражений identical(eval(expr), eval(exprc)) #> [1] TRUE # сравниваем производительность выражений microbenchmark(eval(expr), eval(exprc)) #> Unit: nanoseconds #> expr min lq median uq max neval #> eval(expr) 191031 200750 205432 210436 291039 100 #> eval(exprc) 924 1202 1346 1628 11845 100 </syntaxhighlight>

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

КодR

<syntaxhighlight lang="r"># old R version of lapply la1 <- function(X, FUN, ...) { FUN <- match.fun(FUN) if (!is.list(X)) X <- as.list(X) rval <- vector("list", length(X)) for(i in seq(along = X)) rval[i] <- list(FUN(X[[i]], ...)) names(rval) <- names(X) # keep `names' ! return(rval) } # a small variation la2 <- function(X, FUN, ...) { FUN <- match.fun(FUN) if (!is.list(X)) X <- as.list(X) rval <- vector("list", length(X)) for(i in seq(along = X)) { v <- FUN(X[[i]], ...) if (is.null(v)) rval[i] <- list(v) else rval[[i]] <- v } names(rval) <- names(X) # keep `names' ! return(rval) } </syntaxhighlight>

Скомпилируем эти функции в бат-код:

КодR

<syntaxhighlight lang="r">la1c <- cmpfun(la1) la2c <- cmpfun(la2) lapplyc <- cmpfun(lapply) </syntaxhighlight>

Сравним производительность этих функций:

КодR

<syntaxhighlight lang="r">x <- 1:1000 microbenchmark(lapply(x, is.null), la1(x, is.null), la2(x, is.null), lapplyc(x, is.null), la1c(x, is.null), la2c(x, is.null)) #> Unit: microseconds #> expr min lq median uq max neval #> lapply(x, is.null) 173.1 183.2 193.4 205.2 294.4 100 #> la1(x, is.null) 792.3 836.1 861.1 929.9 1807.6 100 #> la2(x, is.null) 965.0 1012.1 1036.6 1085.9 2769.5 100 #> lapplyc(x, is.null) 171.5 182.4 189.1 200.3 265.6 100 #> la1c(x, is.null) 394.2 416.0 428.0 446.9 1217.8 100 #> la2c(x, is.null) 397.6 424.5 439.1 457.2 1216.0 100 </syntaxhighlight>

Обращается на себя внимание, что скомпилированная версия функции lapply() не превосходит по производительности оригинальную версию. Происходит это потому, что функция lapply() использует написанную на C, скомпилированную функцию. Также отметим, что скомпилированные версии функций la1() и la2() показываются практически одинаковую производительность, тогда как нескомпилированные версии довольно сильно различались. Это достигается за счёт высокой оптимизации работы циклов при компиляции функций.

Компиляция скриптов

Ещё одна возможность, предоставляемая пакетом compiler - это компиляция R-скриптов. Создание скомпилированных файлов осуществляется с помощью функции \texttt{cmpfile()}, а загрузка скомпилированных файлов с помощью функции loadcmp(). Если выходной файл не указан, то выходной файл имеет тоже имя, что и входной, но с расширением .Rc. Пример использования:

КодR

<syntaxhighlight lang="r">cmpfile("script.R", "script.Rc") loadcmp("script.Rc") </syntaxhighlight>

JIT-компиляция

Помимо компиляции отдельных функций и выражений, пакет compiler предоставляет возможность JIT-компиляции (JIT - just-in-time) или компиляции «на лету», т.е. во время непосредственного выполнения кода. Переключение режима осуществляется с помощью функции enableJIT(). Эта функции имеет всего один аргумент (level), который может принимать одно из трёх значений:

  • 0 --- отключение JIT-компиляции;
  • 1 --- компиляции функций до их первого вызова;
  • 2 --- тоже что и 2 плюс компиляция всех циклов for, while, repeat до их вызова.

Отметим, что при включении JIT-компиляции при первом запуске выполнения кода компиляция всех функций и циклов займёт некоторое время.

Примечания


Ссылки