Как стать автором
Обновить

Как ускорить работу с API на языке R с помощью параллельных вычислений, на примере API Яндекс.Директ (Часть 2)

Время на прочтение19 мин
Количество просмотров3.2K
Всего голосов 14: ↑12 и ↓2+10
Комментарии8

Комментарии 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::* на всех уровнях без необходимости что-то менять.

Спасибо, доработал пример кода в тесте скорости, там как раз идеально вписывается `tweak`, т.к. мы запускаем его в фоновой сессии, а внутри него все распараллеленные функцию запускаются ещё по 4 процесса.
Хорошо.
Заметил я только ещё, что в Windows\Linux скорости обработки в параллельных потоках отличаются заметно. Методы запуска потоков разные.
К примеру на одном и том же моем компе на 40 потоках R под Win10 скорость обработки в data.table — 6K строк/сек., а тот же код на этой же машине, но из-под WSL(Debian) — уже 12,6K строк/сек!
Спасибо, в самом деле всё, что в статье описано было проверено только на Windows 10. Добавляю эту инфу.

Спасибо вам за полезные комментарии и плюс в карму.
Добавил пример для Linux с mclapply.
Полезная статья, спасибо! В результате, хм, parallel даже победил. Мне кажется под Unix меньше всяких посторонних процессов, поэтому чище идет, меньше оверхеда… ИМХО.
Тут в тесте всё запускалось под виндой, но на самом деле как я и написал в статье победа parallel скорее случайность. Тут всё зависело от скорости обработки результатов сервером Яндекса, и от нагрузки на него. Поэтому единственное, что я на самом деле тестом скорости хотел показать, что даже на 4ёх аккаунтах скорость работы двухкратно увеличивается при распараллеливании.

Про `mcapply` я написал для общей инфы. У меня всё на винде развёрто, и `mcapply`я тестил вообще в rstudio.cloud, поэтому в статье даже замер времени выполнения не указывал.

Вам ещё раз спасибо за комментарий к первой части статьи!
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории