Como otimizar meu script R para usar "multicore"

15

Estou usando o GNU R em um PC Ubuntu-Lucid que possui 4 CPUs. Para usar todas as 4 CPUs, instalei o pacote "r-cran-multicore". Como o manual do pacote carece de exemplos práticos que eu entendo, preciso de conselhos sobre como otimizar meu script para fazer uso de todas as 4 CPUs.

Meu conjunto de dados é um data.frame (chamado P1) que possui 50.000 linhas e 1600 cols. Para cada linha, eu gostaria de calcular a máxima, soma e média. Meu script é o seguinte:

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,]))
}

Alguém poderia me dizer como modificar e executar o script para usar todas as 4 CPUs?

Produnis
fonte
há um erro no programa acima: a linha deve ser "para (i em 1: comprimento)"
Simon Byrne
você está bem, thx!
Produnis 19/01/11
1
isso não pertence ao StackOverflow?
precisa saber é o seguinte
1
Isso pertence ao StackOverflow. Não há nenhuma pergunta estatística aqui. Apenas uma questão de programação geral.
JD Longo

Respostas:

11

Use foreach e doMC . A explicação detalhada pode ser encontrada aqui . Seu script mudará muito pouco, a linha

for(i in 1:plength){

deve ser alterado para

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

Os pré-requisitos para qualquer script multitarefa usando esses pacotes são

library(foreach)
library(doMC)
registerDoMC()

Nota de cuidado. De acordo com a documentação, você não pode usar isso na GUI.

Quanto ao seu problema, você realmente precisa de multitarefa? Seu data.frame ocupa cerca de 1,2 GB de RAM, portanto deve caber na sua memória. Então você pode simplesmente usar o Apply:

p1smry <- apply(P1,1,summary)

O resultado será uma matriz com resumos de cada linha.

Você também pode usar a função mclapply, que está no pacote multicore. Então seu script pode ficar assim:

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

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

Isso retornará a lista, onde o i-ésimo elemento será o resumo da i-ésima linha. Você pode convertê-lo em matriz usando sapply

mres <- sapply(res,function(x)x)
mpiktas
fonte
Muito obrigado. Você está certo, que com "aplicar" o script pode ser otimizado. Acabei de usar meu script como um exemplo mínimo para passar a mensagem ... Muito, sua resposta é exatamente o que eu estava procurando !!
Produnis
15

Você já tem uma resposta sobre como usar mais de um núcleo, mas o verdadeiro problema está na maneira como você escreveu seus loops. Nunca estenda seu vetor / objeto resultante a cada iteração de um loop . Se você fizer isso, forçará R a copiar o vetor / objeto resultante e estendê-lo, o que leva tempo. Em vez disso, pré-aloque espaço de armazenamento suficiente antes de iniciar o loop e preencher à medida que avança. Aqui está um exemplo:

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

Ou você pode fazer essas coisas através de 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 

Mas observe que isso não é mais rápido do que fazer o loop corretamente e, às vezes, mais lento.

No entanto, esteja sempre atento ao código vetorizado. Você pode fazer somas e meios de linha usando rowSums()e rowMeans()que são mais rápidos que o loop ou as applyversões:

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

   user  system elapsed 
  0.001   0.000   0.002 

Se eu fosse um apostador, teria dinheiro com a terceira abordagem que menciono bater foreach()ou com as outras opções de múltiplos núcleos em um teste de velocidade em sua matriz, porque elas teriam que acelerar as coisas consideravelmente para justificar a sobrecarga incorrida na configuração do processos separados criados nos diferentes núcleos da CPU.

Atualização: Após o comentário de @shabbychef, é mais rápido fazer as somas uma vez e reutilizar no cálculo da média?

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

   user  system elapsed 
  0.002   0.000   0.002

Não nesta execução de teste, mas isso está longe de ser exaustivo ...

Restabelecer Monica - G. Simpson
fonte
FWIW, o Matlab tem os mesmos problemas em relação à pré-alocação e expansão de vetores e é um código clássico 'blooper'. Além da sua aposta, é provavelmente mais rápido usar os resultados rowSumspara calcular os meios de linha (a menos que eu esteja perdendo algo relacionado a, por exemplo, Na ou NaN). O código na sua terceira abordagem soma cada coluna duas vezes .
21711 shabbychef
@shabbychef você ficará surpreso (veja minha resposta editada). Sim as somas são teoricamente calculado duas vezes, mas rowSumse rowMeanssão altamente otimizado código compilado e o que ganhamos em apenas calcular as somas uma vez, perdemos de novo em fazer o cálculo médio no código interpretado.
Reintegrar Monica - G. Simpson
@ Gavin Simpson: não tão rápido: tente sim system.time({ for (iii in c(1:1000)) { p1max3 <- apply(p1, 1, max) p1mean3 <- rowMeans(p1) p1sum3 <- rowSums(p1) } })e da mesma forma system.time({ for (iii in c(1:1000)) { p1max4 <- apply(p1, 1, max) p1sum4 <- rowSums(p1) p1mean4 <- p1sum4 / ncol(p1) } }); a versão que não recalcula a soma leva 1.368 segundos no meu computador; o que faz leva 1.396. mais uma vez, longe de ser exaustiva, mas mais atraente ...
shabbychef
@shabbychef devemos ter idéias diferentes sobre o que é ou não é convincente ;-) Na verdade, suas simulações mais rigorosos reforçar meu ponto principal, que, como rowMeanse rowSumssão implementados em código eficiente, otimizado compilado eles estão vai ser difícil de bater.
Reintegrar Monica - G. Simpson
@Gavin Simpson. Na verdade, o problema com o meu exemplo é que a maior parte do tempo é usada na parte de aplicação para calcular o máximo. Concordo com você que uma função vetorizada baseada em c rowMeanserá difícil de superar por meio de uma ferramenta R de uso geral como *apply. No entanto, você parece sugerir que é mais rápido somar 10000 números duas vezes via rowMeane rowSumnão apenas uma vez e usar o operador de divisão interno de R. Eu sei que o R tem alguns problemas de eficiência ( por exemplo, a recente descoberta de chaves entre parênteses), mas isso parece loucura.
shabbychef
1

Dê uma olhada nos pacotes de neve e queda de neve . Muitos exemplos com esses ...

Se você deseja acelerar esse código específico em vez de aprender sobre R e paralelismo, faça isso

P1 = matrix(rnorm(1000), ncol=10, nrow=10
apply(P1, 1, max)
apply(P1, 1, mean)
apply(P1, 1, sum)
Dr G
fonte
por favor me ajudar a modificar meu script ...
Produnis
2
Aqueles estão apenas escondendo o laço de você. O verdadeiro problema com o código @Produnis é que a cópia forçada está ocorrendo porque os vetores de resultados estão sendo estendidos a cada iteração do loop.
Reintegrar Monica - G. Simpson
pacote de queda de neve pode estender a solução de Gavin como dizer "bolo". O pacote possui uma infinidade de funções de aplicação modificadas para fazer multicores. Para a função de aplicação, você usaria sfApply (<seus argumentos como para aplicar>). A queda de neve também está bem documentada. Devo salientar que nenhum software extra é necessário para fazer isso em um processador com vários núcleos. Consulte stackoverflow.com/questions/4164960/… para obter um exemplo do sfLapply.
Roman Luštrik 19/01/11