Производительность .Primitive и .Internal
Я делал некоторую оптимизацию, удаляя один шаг из процесса...
> library(microbenchmark)
> microbenchmark(paste0("this","and","that"))
Unit: microseconds
expr min lq mean median uq max neval
paste0("this", "and", "that") 2.026 2.027 3.50933 2.431 2.837 34.038 100
> microbenchmark(.Internal(paste0(list("this","and","that"),NULL)))
Unit: microseconds
expr min lq mean median uq max neval
.Internal(paste0(list("this", "and", "that"), NULL)) 1.216 1.621 2.77596 2.026 2.027 43.764 100
пока что так хорошо...
но после того, как я заметил, что list
определяется как
function (...) .Primitive("list")
Я попытался далее "упростить"
> microbenchmark(.Internal(paste0(.Primitive("list")("this","and","that"),NULL)))
Unit: microseconds
expr min lq mean median uq max neval
.Internal(paste0(.Primitive("list")("this", "and", "that"), NULL)) 3.241 3.242 4.66433 3.647 3.648 80.638 100
и время увеличивается!
Я предполагаю, что обработка строки "list"
является источником проблемы и что она обрабатывается по-разному в пределах фактического вызова функции list
но как?
отказ от ответственности: я знаю, что это больно читаемости больше, чем помогает производительности. Это просто для некоторых очень простых функций, которые не будут меняться и использоваться так часто, что небольшие проблемы с производительностью также требуются даже при такой стоимости.
Изменить в ответ на комментарий Джоша О'Брайена:
Я не уверен, что это говорит о его идее, но
library(compiler)
ff <- compile(function(...){.Internal(paste0(.Primitive("list")("this","and","that"),NULL))})
ff2 <- compile(function(...){.Internal(paste0(list("this","and","that"),NULL))})
microbenchmark(eval(ff),eval(ff2),times=10000)
> microbenchmark(eval(ff2),eval(ff),times=10000)
Unit: microseconds
expr min lq mean median uq max neval
eval(ff2) 1.621 2.026 2.356761 2.026 2.431 144.257 10000
eval(ff) 1.621 2.026 2.455913 2.026 2.431 89.148 10000
и глядя на график, созданный с помощью microbenchmark (просто оберните его plot()
, чтобы увидеть его сами), запуская его несколько раз, похоже, что они имеют статистически идентичную производительность, несмотря на то, что значение "max", похожее на ff2, хуже худшего. Не знаю, что с этим делать, но, возможно, это поможет кому-то. Итак, все это в основном говорит о том, что они скомпилируются в идентичный код. Означает ли это, что его комментарий является ответом?
Ответы
Ответ 1
Причина .Internal(paste0(.Primitive("list")("this","and","that"),NULL))
медленнее, по-видимому, из-за того, что догадался Джош О'Брайен. Вызов .Primitive("list")
напрямую вызывает некоторые дополнительные накладные расходы.
Вы можете увидеть эффекты с помощью простого примера:
require(compiler)
pl <- cmpfun({.Primitive("list")})
microbenchmark(list(), .Primitive("list")(), pl())
# Unit: nanoseconds
# expr min lq median uq max neval
# list() 63 98.0 112.0 140.5 529 100
# .Primitive("list")() 4243 4391.5 4486.5 4606.0 16077 100
# pl() 79 135.5 148.0 175.5 39108 100
Тем не менее, вы не сможете улучшить скорость .Primitive
и .Internal
из подсказки R. Они являются точками входа в код C.
И нет причин попробовать и заменить вызов .Primitive
на .Internal
. Это рекурсивно, так как .Internal
сам является примитивным.
> .Internal
function (call) .Primitive(".Internal")
Вы получите ту же медлительность, если попытаетесь вызвать .Internal
"напрямую"... и аналогичную "ускорение", если вы скомпилируете "прямой" вызов.
Internal. <- function() .Internal(paste0(list("this","and","that"),NULL))
Primitive. <- function() .Primitive(".Internal")(paste0("this","and","that"),NULL)
cPrimitive. <- cmpfun({Primitive.})
microbenchmark(Internal., Primitive., cPrimitive., times=1e4)
# Unit: nanoseconds
# expr min lq median uq max neval
# Internal. 26 27 27 28 1057 10000
# Primitive. 28 32 32 33 2526 10000
# cPrimitive. 26 27 27 27 1706 10000
Ответ 2
У интерпретатора R есть жестко запрограммированные оптимизации для общих функций, и это идет глубже, чем байтовая компиляция:
> list2 <- list
> list3 <- cmpfun(list2)
> microbenchmark(
+ list(1,2),
+ list2(1,2),
+ list3(1,2)
+ )
Unit: nanoseconds
expr min lq mean median uq max neval
list(1, 2) 576 620.5 654.53 640.0 675.5 941 100
list2(1, 2) 619 702.0 1123.43 728.0 761.0 39045 100
list3(1, 2) 617 683.0 735.83 715.5 759.0 1964 100
Вот как выглядят SEXP. Обратите внимание на метаданные в "списке"
> .Internal(inspect(quote(list(1,2))))
@23b0ed0 06 LANGSXP g0c0 [NAM(2)]
@1ed8f48 01 SYMSXP g1c0 [MARK,LCK,gp=0x4000] "list" (has value)
@2c7adf8 14 REALSXP g0c1 [] (len=1, tl=0) 1
@2c7adc8 14 REALSXP g0c1 [] (len=1, tl=0) 2
list2
отсутствуют некоторые метаданные:
> list2 <- list
> .Internal(inspect(quote(list2(1,2))))
@23b1578 06 LANGSXP g0c0 [NAM(2)]
@23b0a70 01 SYMSXP g0c0 [] "list2"
@2c7ad08 14 REALSXP g0c1 [] (len=1, tl=0) 1
@2c7acd8 14 REALSXP g0c1 [] (len=1, tl=0) 2
.Primitive("list")
является более сложным выражением:
> .Internal(inspect(quote(.Primitive("list")(1,2))))
@297e748 06 LANGSXP g0c0 [NAM(2)]
@297d9a0 06 LANGSXP g0c0 []
@1ec4530 01 SYMSXP g1c0 [MARK,LCK,gp=0x4000] ".Primitive" (has value)
@2c7a888 16 STRSXP g0c1 [] (len=1, tl=0)
@1ed5588 09 CHARSXP g1c1 [MARK,gp=0x61] [ASCII] [cached] "list"
@2c7a858 14 REALSXP g0c1 [] (len=1, tl=0) 1
@2c7a828 14 REALSXP g0c1 [] (len=1, tl=0) 2