Я написал простое матричное умножение для тестирования возможностей многопоточности/параллелизации моей сети, и я заметил, что вычисление было намного медленнее, чем ожидалось.
Тест прост: умножьте 2 матрицы (4096x4096) и верните время вычисления. Ни матрицы, ни результаты не сохраняются. Время вычисления не является тривиальным (50-90 секунд в зависимости от вашего процессора).
Условия: я повторил это вычисление 10 раз с использованием 1 процессора, разделил эти 10 вычислений на 2 процессора (по 5 штук), затем 3 процессора,... до 10 процессоров (1 вычисление до каждый процессор). Я ожидал, что общее время вычисления уменьшится поэтапно, и я ожидал, что 10 процессоров завершат вычисления 10 раз так быстро, как требуется одному процессору сделать то же самое.
Результаты. Вместо этого я получил только 2-кратное сокращение времени вычисления, которое в 5 раз SLOWER, чем ожидалось.
Когда я вычислил среднее время вычисления на node, я ожидал, что каждый процессор вычислит тест за такое же время (в среднем) независимо от количества назначенных процессоров. Я был удивлен, увидев, что просто отправка одной операции на несколько процессоров замедляет среднее время вычисления каждого процессора.
Может ли кто-нибудь объяснить, почему это происходит?
Обратите внимание, что это вопрос НЕ дубликат этих вопросов:
foreach% dopar% медленнее, чем для цикла
или
Почему параллельный пакет работает медленнее, чем просто использовать?
Поскольку тестовое вычисление не является тривиальным (т.е. 50-90 сек. не 1-2 сек.) и потому, что между обработанными процессорами я не вижу связи (т.е. результаты не возвращаются или не сохраняются иначе, чем время вычисления).
Я добавил скрипты и функции для репликации.
library(foreach); library(doParallel);library(data.table)
# functions adapted from
# http://www.bios.unc.edu/research/genomic_software/Matrix_eQTL/BLAS_Testing.html
Matrix.Multiplier <- function(Dimensions=2^12){
# Creates a matrix of dim=Dimensions and runs multiplication
#Dimensions=2^12
m1 <- Dimensions; m2 <- Dimensions; n <- Dimensions;
z1 <- runif(m1*n); dim(z1) = c(m1,n)
z2 <- runif(m2*n); dim(z2) = c(m2,n)
a <- proc.time()[3]
z3 <- z1 %*% t(z2)
b <- proc.time()[3]
c <- b-a
names(c) <- NULL
rm(z1,z2,z3,m1,m2,n,a,b);gc()
return(c)
}
Nodes <- 10
Results <- NULL
for(i in 1:Nodes){
cl <- makeCluster(i)
registerDoParallel(cl)
ptm <- proc.time()[3]
i.Node.times <- foreach(z=1:Nodes,.combine="c",.multicombine=TRUE,
.inorder=FALSE) %dopar% {
t <- Matrix.Multiplier(Dimensions=2^12)
}
etm <- proc.time()[3]
i.TotalTime <- etm-ptm
i.Times <- cbind(Operations=Nodes,Node.No=i,Avr.Node.Time=mean(i.Node.times),
sd.Node.Time=sd(i.Node.times),
Total.Time=i.TotalTime)
Results <- rbind(Results,i.Times)
rm(ptm,etm,i.Node.times,i.TotalTime,i.Times)
stopCluster(cl)
}
library(data.table)
Results <- data.table(Results)
Results[,lower:=Avr.Node.Time-1.96*sd.Node.Time]
Results[,upper:=Avr.Node.Time+1.96*sd.Node.Time]
Exp.Total <- c(Results[Node.No==1][,Avr.Node.Time]*10,
Results[Node.No==1][,Avr.Node.Time]*5,
Results[Node.No==1][,Avr.Node.Time]*4,
Results[Node.No==1][,Avr.Node.Time]*3,
Results[Node.No==1][,Avr.Node.Time]*2,
Results[Node.No==1][,Avr.Node.Time]*2,
Results[Node.No==1][,Avr.Node.Time]*2,
Results[Node.No==1][,Avr.Node.Time]*2,
Results[Node.No==1][,Avr.Node.Time]*2,
Results[Node.No==1][,Avr.Node.Time]*1)
Results[,Exp.Total.Time:=Exp.Total]
jpeg("Multithread_Test_TotalTime_Results.jpeg")
par(oma=c(0,0,0,0)) # set outer margin to zero
par(mar=c(3.5,3.5,2.5,1.5)) # number of lines per margin (bottom,left,top,right)
plot(x=Results[,Node.No],y=Results[,Total.Time], type="o", xlab="", ylab="",ylim=c(80,900),
col="blue",xaxt="n", yaxt="n", bty="l")
title(main="Time to Complete 10 Multiplications", line=0,cex.lab=3)
title(xlab="Nodes",line=2,cex.lab=1.2,
ylab="Total Computation Time (secs)")
axis(2, at=seq(80, 900, by=100), tick=TRUE, labels=FALSE)
axis(2, at=seq(80, 900, by=100), tick=FALSE, labels=TRUE, line=-0.5)
axis(1, at=Results[,Node.No], tick=TRUE, labels=FALSE)
axis(1, at=Results[,Node.No], tick=FALSE, labels=TRUE, line=-0.5)
lines(x=Results[,Node.No],y=Results[,Exp.Total.Time], type="o",col="red")
legend('topright','groups',
legend=c("Measured", "Expected"), bty="n",lty=c(1,1),
col=c("blue","red"))
dev.off()
jpeg("Multithread_Test_PerNode_Results.jpeg")
par(oma=c(0,0,0,0)) # set outer margin to zero
par(mar=c(3.5,3.5,2.5,1.5)) # number of lines per margin (bottom,left,top,right)
plot(x=Results[,Node.No],y=Results[,Avr.Node.Time], type="o", xlab="", ylab="",
ylim=c(50,500),col="blue",xaxt="n", yaxt="n", bty="l")
title(main="Per Node Multiplication Time", line=0,cex.lab=3)
title(xlab="Nodes",line=2,cex.lab=1.2,
ylab="Computation Time (secs) per Node")
axis(2, at=seq(50,500, by=50), tick=TRUE, labels=FALSE)
axis(2, at=seq(50,500, by=50), tick=FALSE, labels=TRUE, line=-0.5)
axis(1, at=Results[,Node.No], tick=TRUE, labels=FALSE)
axis(1, at=Results[,Node.No], tick=FALSE, labels=TRUE, line=-0.5)
abline(h=Results[Node.No==1][,Avr.Node.Time], col="red")
epsilon = 0.2
segments(Results[,Node.No],Results[,lower],Results[,Node.No],Results[,upper])
segments(Results[,Node.No]-epsilon,Results[,upper],
Results[,Node.No]+epsilon,Results[,upper])
segments(Results[,Node.No]-epsilon, Results[,lower],
Results[,Node.No]+epsilon,Results[,lower])
legend('topleft','groups',
legend=c("Measured", "Expected"), bty="n",lty=c(1,1),
col=c("blue","red"))
dev.off()
РЕДАКТИРОВАТЬ: Ответ @Hong Ooi comment
Я использовал lscpu
в UNIX для получения;
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 30
On-line CPU(s) list: 0-29
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 30
NUMA node(s): 4
Vendor ID: GenuineIntel
CPU family: 6
Model: 63
Model name: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz
Stepping: 2
CPU MHz: 2394.455
BogoMIPS: 4788.91
Hypervisor vendor: VMware
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 20480K
NUMA node0 CPU(s): 0-7
NUMA node1 CPU(s): 8-15
NUMA node2 CPU(s): 16-23
NUMA node3 CPU(s): 24-29
EDIT: ответ на комментарий @Steve Weston.
Я использую сеть виртуальных машин (но я не администратор) с доступом до 30 кластеров. Я провела тест, который вы предложили. Открыл 5 сеансов R и одновременно выполнял матричное умножение на 1,2... 5 (или так быстро, как я мог накладывать и выполнять). Получены очень похожие результаты до (повторный: каждый дополнительный процесс замедляет все отдельные сеансы). Обратите внимание, что я проверил использование памяти с помощью top
и htop
, и использование никогда не превышало 5% от емкости сети (~ 2.5/64Gb).
Заключение:
Проблема, похоже, специфична для R. Когда я запускаю другие многопоточные команды с другим программным обеспечением (например, PLINK), я не сталкиваюсь с этой проблемой, и параллельный процесс выполняется как ожидается. Я также попробовал запустить выше с Rmpi
и doMPI
с такими же (более медленными) результатами. По-видимому, проблема связана с R
сеансами/параллельными командами в сети виртуальных машин. Мне очень нужна помощь, чтобы точно определить проблему. Аналогичная проблема, кажется, указана здесь