Когда (если когда-нибудь) я должен сказать R параллельно не использовать все ядра?

Я использовал этот код:

library(parallel)
cl <- makeCluster( detectCores() - 1)
clusterCall(cl, function(){library(imager)})

тогда у меня есть функция-обертка, которая выглядит примерно так:

d <- matrix  #Loading a batch of data into a matrix
res <- parApply(cl, d, 1, FUN, ...)
# Upload `res` somewhere

Я тестировал на своем ноутбуке, с 8 ядрами (4 ядра, гиперпоточность). Когда я запустил его на 50 000 строк, 800 столбцов, матрицы, для его завершения потребовалось 177,5 с, и большую часть времени 7 ядер держались на уровне около 100% (согласно верхнему уровню), а затем он оставался там в течение последних 15 или около того секунд, которые, я думаю, сочетал результаты. В соответствии с system.time(), пользовательское время было 14 с, так что совпадает.

Сейчас я работаю на EC2, 36-ядерном c4.8xlarge, и вижу, что он проводит почти все свое время с одним ядром на 100%. Точнее: примерно 10-20 секунд происходит всплеск, когда используются все ядра, затем около 90 секунд только одного ядра при 100% (используется R), затем около 45 секунд других вещей (где я сохраняю результаты и загрузить следующую партию данных). Я делаю партии из 40000 строк, 800 столбцов.

Долгосрочная средняя нагрузка, по данным top, колеблется около 5.00.

Это кажется разумным? Или есть момент, когда параллелизм R тратит больше времени на коммуникационные издержки, и я должен ограничиться, например, 16 ядрами. Здесь есть какие-то правила?

Ссылка: спецификация процессора, которую я использую "Linux 4.4.5-15.26.amzn1.x86_64 (amd64)". Версия R 3.2.2 (2015-08-14)

ОБНОВЛЕНИЕ: я попробовал с 16 ядрами. Для самых маленьких данных время выполнения увеличилось с 13,9 до 18,3 с. Для средних данных:

With 16 cores:
   user  system elapsed 
 30.424   0.580  60.034 

With 35 cores:
   user  system elapsed 
 30.220   0.604  54.395 

Т.е. на служебную часть ушло столько же времени, но на параллельном бите было меньше ядер, поэтому на это потребовалось больше времени, и в целом на это потребовалось больше времени.

Я также пытался использовать mclapply(), как предлагается в комментариях. Казалось, что это было немного быстрее (что-то вроде 330 с против 360 с на конкретных тестовых данных, на которых я его пробовал), но это было на моем ноутбуке, где другие процессы или перегрев могли повлиять на результаты. Так что я пока не делаю никаких выводов по этому поводу.

2 ответа

Решение

Полезных правил не существует - количество ядер, для которых оптимальна параллельная задача, целиком определяется этой задачей. Для более общего обсуждения см . Закон Густафсона.

Высокая одноядерная часть, которую вы видите в своем коде, вероятно, происходит из конечной фазы алгоритма (фазы "соединения"), где параллельные результаты объединяются в единую структуру данных. Поскольку это намного превосходит фазу параллельных вычислений, это действительно может указывать на то, что меньшее количество ядер может быть полезным.

Я бы добавил, что если вы не знаете об этом замечательном ресурсе для параллельных вычислений в R, вы можете прочитать недавнюю книгу Нормана Мэтлоффа. Parallel Computing for Data Science: With Examples in R, C++ and CUDA очень полезное чтение. Я очень рекомендую это (я многому научился, не исходя из опыта CS).

Книга подробно отвечает на ваш вопрос (в частности, глава 2). Книга дает общий обзор причин накладных расходов, которые приводят к узким местам в параллельных программах.

Цитирую раздел 2.1, который неявно частично отвечает на ваш вопрос:

В параллельном программировании есть две основные проблемы производительности:

Затраты на связь. Обычно данные должны передаваться между процессами. Это требует времени, что может сильно сказаться на производительности. Кроме того, процессы могут мешать друг другу, если все они пытаются получить доступ к одним и тем же данным одновременно. Они могут сталкиваться при попытке доступа к одному и тому же каналу связи, одному и тому же модулю памяти и т. Д. Это еще один сок на скорости. Термин гранулярность используется для приблизительного отношения к соотношению вычислений и накладных расходов. Крупномасштабные или грубозернистые алгоритмы включают в себя достаточно большие куски вычислений, поэтому накладные расходы не представляют большой проблемы. В детализированных алгоритмах нам действительно нужно максимально избегать накладных расходов.

^ Когда накладные расходы высоки, меньшее количество ядер для рассматриваемой проблемы может дать более короткое общее время вычислений.

Балансировка нагрузки. Как отмечалось в предыдущей главе, если мы не будем осторожны в том, как мы назначаем работу процессам, мы рискуем назначить гораздо больше работы некоторым, чем другим. Это ухудшает производительность, так как оставляет некоторые процессы непродуктивными в конце цикла, в то время как работа еще не завершена.

Когда, если вообще не использовать все ядра? Один пример из моего личного опыта ежедневного выполнения cronjobs в R для данных, которые составляют 100-200 ГБ данных в ОЗУ, в которых несколько ядер запускаются для обработки блоков данных, я действительно обнаружил, работая с, скажем, 6 из 32 доступных ядер. быть быстрее, чем использовать 20-30 ядер. Основной причиной были требования к памяти для дочерних процессов (после того, как определенное количество дочерних процессов было в действии, использование памяти стало высоким, и вещи значительно замедлились).

Другие вопросы по тегам