Як оптимізувати свій R-скрипт, щоб використовувати "багатоядерний"


15

Я використовую GNU R на комп'ютері Ubuntu-Lucid, який має 4 процесора. Для використання всіх 4 процесорів я встановив пакет "r-cran-multicore". Оскільки в посібнику з пакета бракує практичних прикладів, які я розумію, мені потрібні поради щодо оптимізації мого сценарію для використання всіх 4 процесорів.

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

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 процесора?


у наведеній програмі є помилка: рядок повинен бути "для (i in 1: plength)"
Simon Byrne

ти rigth, thx!
Проніс

1
це не належить до StackOverflow?
R_Coholic

1
Це належить до StackOverflow. Тут взагалі немає питань щодо статистики. Лише загальне питання програмування.
JD Довгий

Відповіді:


11

Використовуйте foreach та doMC . Детальне пояснення можна знайти тут . Ваш сценарій зміниться дуже мало, рядок

for(i in 1:plength){

слід змінити на

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

Передумовами будь-якого багатозадачного сценарію за допомогою цих пакетів є

library(foreach)
library(doMC)
registerDoMC()

Зверніть увагу на обережність. Згідно з документацією, ви не можете використовувати це в GUI.

Що стосується вашої проблеми, чи справді вам потрібна багатозадачність? Ваш data.frame займає близько 1,2 ГБ оперативної пам’яті, тому він повинен вміститися у вашій пам’яті. Тож ви можете просто застосувати:

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)

дуже тобі дякую. Ви маєте рацію, що при застосуванні сценарій можна було б оптимізувати. Я просто використав свій сценарій як мінімальний приклад для того, щоб отримати повідомлення через ... Дуже багато, ваша відповідь саме те, що я шукав !!
Produnis

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

@shabbychef ви здивуєтеся (див. мою відредаговану відповідь). Так суми умоглядно обчислюються двічі, але rowSumsі rowMeansвисоко оптимізовані скомпільований код , і що ми отримуємо тільки в обчисленні суми один раз, ми знову втратити при цьому середньому обчисленні в интерпретируемом коді.
Відновіть Моніку - Г. Сімпсон

@Gavin Simpson: не так швидко: спробуйте замість цього 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,368 секунд; той, що робить, займає 1,396. знову ж таки, далеко не вичерпний, але більш переконливий ...
shabbychef

@shabbychef у нас повинні бути різні ідеї щодо того, що є чи не є переконливим ;-) Насправді, ваші більш жорсткі симуляції підкреслюють мою головну думку, що як rowMeansі rowSumsреалізовані в ефективному, оптимізованому складеному коді, їх буде важко перемогти.
Відновіть Моніку - Г. Сімпсон

@Gavin Simpson. Насправді проблема мого прикладу полягає в тому, що більшу частину часу займає частина застосування для обчислення максимуму. Я погоджуюсь з вами, що векторизовану функцію на основі с начебто rowMeanважко буде перемогти через інструмент R загального призначення, як *apply. Тим НЕ менше, ви , здається, припускають , що він швидше підводити 10000 чисел двічі через rowMeanта , rowSumа не тільки один раз і оператор вбудованого поділу використання R в. Я знаю, що R має деякі проблеми ефективності ( наприклад, недавнє відкриття фігурних дужок проти дужок), але це здається божевільним.
shabbychef

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.