почему purrr :: map2 такой медленный по сравнению с base mapply? - PullRequest
0 голосов
/ 30 мая 2018

Рассмотрим этот простой тест

list1 <- as.list(rep(1, 50))
list2 <- as.list(rep(1, 50))

microbenchmark::microbenchmark(
+   map2(list1, list2, sum))
Unit: microseconds
                    expr    min       lq     mean   median      uq     max neval
 map2(list1, list2, sum) 375.31 384.2045 481.8708 407.8115 420.641 7923.58   100

microbenchmark::microbenchmark(
+   mapply(sum, X=list1, Y=list2,  SIMPLIFY = FALSE))
Unit: microseconds
                                                expr    min     lq     mean  median      uq    max neval
 mapply(sum, X = list1, Y = list2, SIMPLIFY = FALSE) 46.187 50.634 57.45634 53.3715 59.8715 127.27   100

Почему purrr:map2 почти 8 раз медленнее , чем mapply здесь?Я имею в виду, я просто суммирую числа в двух списках.

Проблема в том, что я использую map2 в своем текущем коде, поэтому я хотел бы понять, какие здесь издержки (и как это возможно исправить)

Спасибо!

1 Ответ

0 голосов
/ 31 мая 2018

Как отметил @ eipi10 в комментариях, некоторые служебные вызовы функций становятся менее важными, когда используются большие объемы данных:

list1 <- as.list(rep(1, 50000))
list2 <- as.list(rep(1, 50000))
microbenchmark(map2(list1, list2, sum), mapply(sum, X=list1, Y=list2,  SIMPLIFY = FALSE))
Unit: milliseconds
                                                expr      min       lq     mean   median       uq      max neval cld
                             map2(list1, list2, sum) 73.84420 78.21917 82.53853 79.48526 81.28048 218.9266   100   b
 mapply(sum, X = list1, Y = list2, SIMPLIFY = FALSE) 51.92849 54.66514 61.34755 56.99206 58.67459 204.2119   100  a 

mapply использует .Internal, а purr::map2 использует.Call для доступа к базовым функциям C, которые выполняют обработку.Существуют некоторые различия в том, как они работают, особенно в отношении оценки аргументов и в том, как R ищет основной код.

Справка R по .Internal дает загадочное сообщение:

.Internal выполняет вызов внутреннего кода, встроенного в интерпретатор R.

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

Однако в руководстве по внутренним ресурсам R объясняется, что:

Код C, скомпилированный в R во время сборки, может вызываться непосредственно в так называемых примитивах или через интерфейс .Internal, который очень похож на интерфейс .External, за исключением синтаксиса.Точнее, R поддерживает таблицу имен функций R и соответствующих функций C для вызова, которые по соглашению все начинаются с 'do_' и возвращают SEXP.Эта таблица (R_FunTab в файле src / main / names.c) также указывает, сколько аргументов функции требуется или разрешено, должны ли аргументы оцениваться перед вызовом, и является ли функция «внутренней» в смыслечто он должен быть доступен через интерфейс .Internal или напрямую доступен; в этом случае он печатается в R как .Primitive.

и

Небольшое количество примитивовявляются специальными, а не встроенными, то есть они вводятся с неоцененными аргументами.Это явно необходимо для языковых конструкций и операторов присваивания, а также для && и ||которые условно оценивают свой второй аргумент, и ~, .Internal, call, expression, missing, on.exit, quote и replace, которые не оценивают некоторые из своих аргументов.

Файл справки для .Call notes:

Если одна из этих функций будет использоваться часто, укажите PACKAGE (чтобы ограничить поиск одной DLL) или передайте .NAME в качестве одного из собственных объектов символа.Поиск символов может занять много времени, особенно когда загружено много пространств имен.

Это означает, что при использовании .Call уходит некоторое время на поиск DLL-библиотек для функций.Стоит отметить, что purr::map2 не указывает имя пакета при использовании .Call, и это может уменьшить необходимые служебные данные.

...