Комментарии 8
По смыслу и синтаксису основные функции пакета очень похожи на sapply, основное его преимущество заключается в следующем:
Функции разделены на семейства map, map2, pmap, walk и так далее, отдельные функции входящие в одно семейство возвращают результат в разных форматах: chr, dbl, int, df и т.д.;
Функции семейства map2 позволяют перебирать элементы (итерироваться) одновременно двух объектов;
Функции семейства pmap позволяют одновременно перебирать элементы любого количества объектов. На вход в аргумент .l (аналог аргумента X в sapply) вы можете передавать таблицу, каждый столбец которой будет содержать значения, по которым вы будете итерироваться, и которые будут подставлены по очереди в одноимённые аргументы функции, переданной в .f (аналог FUN из sapply).
Мне всегда казалось, что одним из основных аргументов в пользу использования purrr
вместо стандартных *apply
это consistency в плане аргументов функций и выдаваемых результатов. Мне всегда было довольно сложно помнить всю эту кашу (см. документацию к *apply
). purrr
же решает одну задачу за раз. Применить функцию? Пожалуйста. Упростить и преобразовать результат? Вот вам перегрузка или дополнительный flatten
в конце. В качестве бонуса — упрощение работы с анонимными функциями через простые формулы.
У future
есть, возможно, неочевидная фича, которая вряд ли пригодится в простых задачах "распараллель это". С помощью tweak
можно задать целую иерархию процессов. Если не ошибаюсь, то вот такой фрагмент кода
plan(list(tweak(cluster, workers = 2), tweak(cluster, workers = 3)))
Создаст два процесса верхнего уровня, каждый из которых породит еще по 3 (в сумме 8). Теперь первый вызов furrr::*
распараллелится на два процесса, при этом каждый вложенный furrr::*
в свою очередь будет выполняется на своих 3 child-процессах.
Вот примерный вывод для моего окружения (MRAN R 3.5.1
)
future_map(1:2, function(x) list(Sys.getpid(), future_map_int(1:3, ~Sys.getpid())))
[[1]]
[[1]][[1]]
[1] 23720
[[1]][[2]]
[1] 24764 18628 12708
[[2]]
[[2]][[1]]
[1] 13580
[[2]][[2]]
[1] 5032 24844 8508
Мне это помогало в случае когда уже есть готовый метод, вклчюающий как долгие однопоточные вычисления, так и хорошо распараллеленные вычисления. Теперь, если такой метод нужно применить к нескольким независимым датасетам, то вариантов два — либо запускать последовательно для каждого датасета (это значит что на время однопоточного вычисления мы используем только один поток и получаем бонус только когда достигаем распараллеленного кода), либо запускать параллельно сразу для всех датасетов (тогда и однопоточный, и распараллеленный код будут всегда работать в одном потоке). С иерархическим подходом можно контролировать степень параллелизации и в целом ускорить выполнение такого нетриваильного алгоритма. Все это используя future::*
на всех уровнях без необходимости что-то менять.
Заметил я только ещё, что в Windows\Linux скорости обработки в параллельных потоках отличаются заметно. Методы запуска потоков разные.
К примеру на одном и том же моем компе на 40 потоках R под Win10 скорость обработки в data.table — 6K строк/сек., а тот же код на этой же машине, но из-под WSL(Debian) — уже 12,6K строк/сек!
Про `mcapply` я написал для общей инфы. У меня всё на винде развёрто, и `mcapply`я тестил вообще в rstudio.cloud, поэтому в статье даже замер времени выполнения не указывал.
Вам ещё раз спасибо за комментарий к первой части статьи!
Как ускорить работу с API на языке R с помощью параллельных вычислений, на примере API Яндекс.Директ (Часть 2)