Почему параллельный пакет работает медленнее, чем просто использовать?
Я пытаюсь определить, когда использовать пакет parallel
, чтобы ускорить время, необходимое для выполнения некоторого анализа. Одна из вещей, которые мне нужно сделать, - это создать матрицы, сравнивающие переменные в двух фреймах данных с различным количеством строк. Я задал вопрос об эффективном способе работы в Qaru и написал о тестах в своем блоге. Поскольку я доволен лучшим подходом, я хотел ускорить процесс, запустив его параллельно. Приведенные ниже результаты основаны на 2 ГГц i7 Mac с 8 ГБ оперативной памяти. Я удивлен, что пакет parallel
, в частности, функция parSapply
, хуже, чем просто использование функции apply
. Код для воспроизведения этого ниже. Обратите внимание, что в настоящее время я использую только один из двух создаваемых мной столбцов, но в итоге хочу использовать оба.
![Execution Time]()
(источник: bryer.org)
require(parallel)
require(ggplot2)
require(reshape2)
set.seed(2112)
results <- list()
sizes <- seq(1000, 30000, by=5000)
pb <- txtProgressBar(min=0, max=length(sizes), style=3)
for(cnt in 1:length(sizes)) {
i <- sizes[cnt]
df1 <- data.frame(row.names=1:i,
var1=sample(c(TRUE,FALSE), i, replace=TRUE),
var2=sample(1:10, i, replace=TRUE) )
df2 <- data.frame(row.names=(i + 1):(i + i),
var1=sample(c(TRUE,FALSE), i, replace=TRUE),
var2=sample(1:10, i, replace=TRUE))
tm1 <- system.time({
df6 <- sapply(df2$var1, FUN=function(x) { x == df1$var1 })
dimnames(df6) <- list(row.names(df1), row.names(df2))
})
rm(df6)
tm2 <- system.time({
cl <- makeCluster(getOption('cl.cores', detectCores()))
tm3 <- system.time({
df7 <- parSapply(cl, df1$var1, FUN=function(x, df2) { x == df2$var1 }, df2=df2)
dimnames(df7) <- list(row.names(df1), row.names(df2))
})
stopCluster(cl)
})
rm(df7)
results[[cnt]] <- c(apply=tm1, parallel.total=tm2, parallel.exec=tm3)
setTxtProgressBar(pb, cnt)
}
toplot <- as.data.frame(results)[,c('apply.user.self','parallel.total.user.self',
'parallel.exec.user.self')]
toplot$size <- sizes
toplot <- melt(toplot, id='size')
ggplot(toplot, aes(x=size, y=value, colour=variable)) + geom_line() +
xlab('Vector Size') + ylab('Time (seconds)')
Ответы
Ответ 1
Выполнение заданий параллельно приводит к накладным расходам. Только если рабочие места, которые вы запускаете на рабочих узлах, занимаете значительное количество времени, распараллеливание повышает общую производительность. Когда отдельные задания занимают всего миллисекунды, накладные расходы на постоянное отключение рабочих мест ухудшат общую производительность. Трюк состоит в том, чтобы разделить работу над узлами таким образом, чтобы задания были достаточно длинными, скажем, по крайней мере несколько секунд. Я использовал это с большим успехом, работая одновременно с шестью моделями Fortran, но эти отдельные модели выполняли часы, почти отрицая эффект накладных расходов.
Обратите внимание, что я не запускал ваш пример, но ситуация, о которой я рассказываю выше, часто является проблемой, когда парализация занимает больше времени, чем выполнение последовательно.
Ответ 2
Эти различия могут быть связаны с 1) коммуникационными издержками (особенно, если вы используете узлы) и 2) служебные данные по производительности (если ваша работа не такая интенсивная по сравнению с инициированием параллелизации, например). Обычно, если задача, которую вы распараллеливаете, не требует много времени, тогда вы в основном обнаружите, что параллелизация не имеет большого эффекта (что очень заметно на огромных наборах данных.
Несмотря на то, что это не может напрямую ответить на ваш бенчмаркинг, я надеюсь, что это должно быть довольно простым и связанным с ним. В качестве примера здесь я построю строки data.frame
с 1e6
с 1e4
уникальными столбцами group
и некоторыми значениями в столбце val
. И затем я запускаю с помощью plyr
в parallel
с помощью doMC
и без распараллеливания.
df <- data.frame(group = as.factor(sample(1:1e4, 1e6, replace = T)),
val = sample(1:10, 1e6, replace = T))
> head(df)
group val
# 1 8498 8
# 2 5253 6
# 3 1495 1
# 4 7362 9
# 5 2344 6
# 6 5602 9
> dim(df)
# [1] 1000000 2
require(plyr)
require(doMC)
registerDoMC(20) # 20 processors
# parallelisation using doMC + plyr
P.PLYR <- function() {
o1 <- ddply(df, .(group), function(x) sum(x$val), .parallel = TRUE)
}
# no parallelisation
PLYR <- function() {
o2 <- ddply(df, .(group), function(x) sum(x$val), .parallel = FALSE)
}
require(rbenchmark)
benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")
test replications elapsed relative user.self sys.self user.child sys.child
2 PLYR() 2 8.925 1.000 8.865 0.068 0.000 0.000
1 P.PLYR() 2 30.637 3.433 15.841 13.945 8.944 38.858
Как вы можете видеть, параллельная версия plyr
работает в 3,5 раза медленнее
Теперь позвольте мне использовать тот же data.frame
, но вместо вычисления sum
позвольте мне построить более сложную функцию, скажем, median(.) * median(rnorm(1e4)
((бессмысленно, да):
Вы увидите, что приливы начинают смещаться:
# parallelisation using doMC + plyr
P.PLYR <- function() {
o1 <- ddply(df, .(group), function(x)
median(x$val) * median(rnorm(1e4)), .parallel = TRUE)
}
# no parallelisation
PLYR <- function() {
o2 <- ddply(df, .(group), function(x)
median(x$val) * median(rnorm(1e4)), .parallel = FALSE)
}
> benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")
test replications elapsed relative user.self sys.self user.child sys.child
1 P.PLYR() 2 41.911 1.000 15.265 15.369 141.585 34.254
2 PLYR() 2 73.417 1.752 73.372 0.052 0.000 0.000
Здесь версия parallel имеет 1.752 times
быстрее, чем непараллельная версия.
Изменить: Следуя комментарию @Paul, я просто выполнил небольшую задержку с помощью Sys.sleep()
. Конечно, результаты очевидны. Но только ради полноты, вот результат на 20 * 2. data.frame:
df <- data.frame(group=sample(letters[1:5], 20, replace=T), val=sample(20))
# parallelisation using doMC + plyr
P.PLYR <- function() {
o1 <- ddply(df, .(group), function(x) {
Sys.sleep(2)
median(x$val)
}, .parallel = TRUE)
}
# no parallelisation
PLYR <- function() {
o2 <- ddply(df, .(group), function(x) {
Sys.sleep(2)
median(x$val)
}, .parallel = FALSE)
}
> benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")
# test replications elapsed relative user.self sys.self user.child sys.child
# 1 P.PLYR() 2 4.116 1.000 0.056 0.056 0.024 0.04
# 2 PLYR() 2 20.050 4.871 0.028 0.000 0.000 0.00
Разница здесь не удивительна.
Ответ 3
Полностью согласен с аргументами @Arun и @PaulHiemestra о том, почему...? часть вашего вопроса.
Однако, похоже, вы можете воспользоваться некоторыми преимуществами пакета parallel
в своей ситуации (по крайней мере, если вы не застряли в Windows). Возможное решение использует mclapply
вместо parSapply
, который опирается на быструю динамическую и общую память.
tm2 <- system.time({
tm3 <- system.time({
df7 <- matrix(unlist(mclapply(df2$var1, FUN=function(x) {x==df1$var1}, mc.cores=8)), nrow=i)
dimnames(df7) <- list(row.names(df1), row.names(df2))
})
})
Конечно, вложенный system.time
здесь не нужен. С моими 2 ядрами я получил:
![enter image description here]()