Как оптимизировать мой R скрипт для использования «многоядерности»


15

Я использую GNU R на ПК с Ubuntu-Lucid, который имеет 4 процессора. Чтобы использовать все 4 процессора, я установил пакет «r-cran-multicore». Поскольку в руководстве по пакету отсутствуют практические примеры, которые я понимаю, мне нужен совет, как оптимизировать мой сценарий, чтобы использовать все 4 процессора.

Мой набор данных - это data.frame (называемый P1), который содержит 50 000 строк и 1600 столбцов. Для каждой строки я хотел бы подсчитать максимальную, сумму и среднее. Мой сценарий выглядит следующим образом:

p1max <- 0
p1mean <- 0
p1sum <-0
plength <- length(P1[,1])
for(i in 1:plength){
   p1max <- c(p1max, max(P1[i,]))
   p1mean <- c(p1mean, mean(P1[i,]))
   p1sum <- c(p1sum, sum(P1[i,]))
}

Может ли кто-нибудь сказать мне, как изменить и запустить скрипт, чтобы использовать все 4 процессора?


в приведенной выше программе есть ошибка: строка должна быть "для (я в 1: длина)"
Саймон Бирн

ты прав, спасибо!
Продунис

1
это не относится к StackOverflow?
R_Coholic

1
Это относится к StackOverflow. Здесь нет никакого вопроса статистики. Только общий вопрос программирования.
JD Long

Ответы:


11

Используйте foreach и doMC . Подробное объяснение можно найти здесь . Ваш сценарий очень мало изменится, строка

for(i in 1:plength){

следует изменить на

foreach(i=1:plength) %dopar% { 

Необходимые условия для любого многозадачного скрипта, использующего эти пакеты:

library(foreach)
library(doMC)
registerDoMC()

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

Что касается вашей проблемы, вам действительно нужна многозадачность? Ваш data.frame занимает около 1,2 ГБ ОЗУ, поэтому он должен уместиться в вашей памяти. Так что вы можете просто использовать apply:

p1smry <- apply(P1,1,summary)

Результатом будет матрица с кратким изложением каждой строки.

Вы также можете использовать функцию mclapply, которая находится в многоядерном пакете. Тогда ваш скрипт может выглядеть так:

loopfun <- function(i) {
     summary(P1[i,])
}

res <- mclapply(1:nrow(P1),loopfun)

Это вернет список, где i-й элемент будет сводкой i-й строки. Вы можете преобразовать его в матрицу, используя sapply

mres <- sapply(res,function(x)x)

большое Вам спасибо. Вы правы, что с «apply» скрипт может быть оптимизирован. Я просто использовал свой сценарий в качестве минимального примера, чтобы донести сообщение ... Спасибо, ваш ответ - именно то, что я искал !!
Продунис

15

Вы уже получили ответ о том, как использовать более одного ядра, но реальная проблема заключается в том, как вы пишете свои циклы. Никогда не расширяйте ваш результирующий вектор / объект на каждой итерации цикла . Если вы сделаете это, вы заставите R скопировать ваш вектор / объект результата и расширить его, что занимает много времени. Вместо этого предварительно выделите достаточно места для хранения, прежде чем начинать цикл, и заполните его по мере продвижения. Вот пример:

set.seed(1)
p1 <- matrix(rnorm(10000), ncol=100)
system.time({
p1max <- p1mean <- p1sum <- numeric(length = 100)
for(i in seq_along(p1max)){
   p1max[i] <- max(p1[i,])
   p1mean[i] <- mean(p1[i,])
   p1sum[i ]<- sum(p1[i,])
}
})

   user  system elapsed 
  0.005   0.000   0.005

Или вы можете сделать это через apply():

system.time({
p1max2 <- apply(p1, 1, max)
p1mean2 <- apply(p1, 1, mean)
p1sum2 <- apply(p1, 1, sum)
})
   user  system elapsed 
  0.007   0.000   0.006 

Но обратите внимание, что это не быстрее, чем делать цикл правильно, а иногда и медленнее.

Однако всегда следите за векторизованным кодом. Вы можете использовать суммы строк и средства, используя rowSums()и rowMeans()которые быстрее, чем цикл или applyверсии:

system.time({
p1max3 <- apply(p1, 1, max)
p1mean3 <- rowMeans(p1)
p1sum3 <- rowSums(p1)
})

   user  system elapsed 
  0.001   0.000   0.002 

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

Обновление: после комментария от @shabbychef, быстрее ли сделать суммы один раз и повторно использовать в вычислении среднего?

system.time({
    p1max4 <- apply(p1, 1, max)
    p1sum4 <- rowSums(p1)
    p1mean4 <- p1sum4 / ncol(p1)
    })

   user  system elapsed 
  0.002   0.000   0.002

Не в этом тесте, но это далеко не исчерпывающий ...


FWIW, Matlab имеет те же проблемы, связанные с предварительным распределением и расширением векторов, и является классическим «блокпостом» кода. В дополнение к вашей ставке, возможно, быстрее использовать результаты rowSumsдля вычисления средних значений строк (если я не пропускаю что-то, например, в отношении Na или NaN). Код вашего третьего подхода суммирует каждый столбец дважды .
Шаббычеф

@shabbychef вы будете удивлены (см. мой отредактированный ответ). Да суммы умозрительно вычисляются дважды, но rowSumsи rowMeansвысоко оптимизированные скомпилированный код , и что мы получаем только в вычислении суммы один раз, мы снова потерять при этом среднем вычислении в интерпретируемом коде.
Восстановить Монику - Дж. Симпсон

@ Гэвин Симпсон: не так быстро: попробуйте вместо этого system.time({ for (iii in c(1:1000)) { p1max3 <- apply(p1, 1, max) p1mean3 <- rowMeans(p1) p1sum3 <- rowSums(p1) } })и аналогично system.time({ for (iii in c(1:1000)) { p1max4 <- apply(p1, 1, max) p1sum4 <- rowSums(p1) p1mean4 <- p1sum4 / ncol(p1) } }); версия, которая не пересчитывает сумму, занимает на моем компьютере 1,386 секунды; тот, который делает 1.396. опять же, далеко не исчерпывающий, но более убедительный ...
Шаббычеф

@shabbychef мы должны иметь разные представления о том, что является или не является убедительным ;-) На самом деле, ваши более строгие моделирования укрепить свою основную точку, что , как rowMeansи rowSumsреализованы в эффективном, оптимизированном скомпилированном коде , они будут трудно превзойти.
Восстановить Монику - Дж. Симпсон

@ Гэвин Симпсон. На самом деле, проблема с моим примером состоит в том, что большую часть времени уходит на применение части для вычисления максимума. Я согласен с Вами , что с на основе векторизации функции , как rowMeanбудет трудно превзойти с помощью общего назначения R инструмент , как *apply. Тем не менее, вы , кажется, предполагают , что он быстрее подводить 10000 чисел дважды через rowMeanи , rowSumа не только один раз и оператор встроенного разделения использования R в. Я знаю, что у R есть некоторые проблемы с эффективностью ( например, недавнее обнаружение проблемы фигурных скобок и скобок), но это кажется сумасшедшим.
Шаббычеф

1

Посмотрите на пакеты снега и снегопада . Множество примеров с этими ...

Если вы хотите ускорить этот конкретный код, а не изучать R и параллелизм, вы должны сделать это

P1 = matrix(rnorm(1000), ncol=10, nrow=10
apply(P1, 1, max)
apply(P1, 1, mean)
apply(P1, 1, sum)

Пожалуйста, помогите мне изменить мой сценарий ...
Produnis

2
Те просто прячут петлю от тебя. Настоящая проблема с кодом @Produnis заключается в том, что принудительное копирование происходит потому, что векторы результатов расширяются на каждой итерации цикла.
Восстановить Монику - Г. Симпсон

пакет снегопада может расширить решение Гэвина, как сказать «торт». Пакет имеет множество функций применения, модифицированных для выполнения многоядерности. Для применения функции вы должны использовать sfApply (<ваши аргументы для применения>). Снегопад также хорошо документирован. Следует отметить, что для выполнения этого на многоядерном процессоре не требуется никакого дополнительного программного обеспечения. См stackoverflow.com/questions/4164960/... для примера sfLapply.
Роман Луштрик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.