Acelerando a leitura de muitos de arquivos no R para posterior processamento.
O DATASUS, braço tecnológico do Ministério da Saúde, disponibiliza uma vasta quantidade de informações sobre diversos aspectos do Serviço Unificado de Saúde (SUS). Muitos destes arquivos são disponibilizados em um formato dbc, que é um “compressed DBF”.
Estes arquivos são disponibilizados no site de FTP do DATASUS. Para fins deste pequeno experimento, todos os arquivos foram baixados e estão na máquina local.
O objetivo deste pequeno estudo é experimentar técnicas de paralelização no R para acelerar a leitura dos arquivos.
Nem sempre operações de I/O em paralelo trazem ganho de velocidade especialmente em se tratando de discos rígidos por rotação (os tradicionais), isto porque a operação de busca nestes discos pode incorrer em penalização. Entretanto, neste experimento, obtivemos um bom resultado com a leitura em paralelo.
Carregando as bibliotecas
library(tidyverse)
library(read.dbc)
library(data.table)
library(microbenchmark)
library(parallel)
Todos os arquivos das APACs foram baixados do site FTP do DATASUS.
<- "~/datasets/datasus.gov.br/SIASUS/PA-LaudosDiversos/" diretorio
<- "PA"
PREF # indique o estado ou "" para todos os estados
<- "(AC|AL)"
UF <- "(\\d\\d)"
MESES <- "(08|09|10|11|12)" ANO
<- list.files(diretorio, paste0(PREF,UF,ANO,MESES,".*.dbc")) arquivos
No total, selecionamos 120 arquivos.
$ ls -ltr PA{AC,AL}{08,09,10,11,12}* | wc
120 1080 7560
$ ls -ltr PA{AC,AL}{08,09,10,11,12}* | awk '{total += $5}; END {print total}'
523484797
Ou seja, para estes 120 arquivos, temos um total de 523.484.797 bytes, i.e., pouco mais de 500MB.
Número de cores disponíveis para processamento paralelo. Esta é uma máquina em uma nuvem privada.
<- detectCores()) (numCores
[1] 32
A função makeCluster
é utilizada para criar o nosso cluster virtual de processamento paralelo.
<- makeCluster(numCores) cluster
Criando uma lista com os nomes completos dos arquivos (com o diretório).
<- lapply(arquivos, function(x) {paste0(diretorio,x)})
fNames <- NULL arqs
<- system.time(arqserial <- lapply(fNames[1:12], read.dbc))
ptimes 3] ptimes[
elapsed
7.222
Limpando a memória
<- NULL
arqserial rm(arqserial)
A função mclapply
é o equivalente paralelo da função lapply
que utilizamos acima. mclapply
faz parte do pacote parallel é muito conveniente para uma paralelização trivial de operações que envolvam listas.
library(parallel)
<- system.time(arqs <- parallel::mclapply(fNames, read.dbc, mc.cores = numCores))
ptime 3] ptime[
elapsed
34.175
O tempo para leitura de apenas 12 arquivos foi 7.222 segundos, enquanto o tempo para a leitura de 120 arquivos em paralelo foi 34.175 segundos. Um excelente ganho de velocidade com a leitura paralela.
É importante ressaltar que os arquivos destes dois estados são pequenos. Mesmo esta quantidade de arquivos (120) não comprometeu a memória da máquina. Nem sempre isso ocorre. É preciso tomar cuidado!
Os arquivos de São Paulo são muito maiores que os dos outros estados, de modo que constituem um desafio maior ainda para uma leitura em paralelo: otimizar esta leitura é essencial!
<- "PA"
PREF # indique o estado ou "" para todos os estados
<- "SP"
UF <- "(\\d\\d)"
MESES <- "(10|11|12|13|14|15|16)" ANO
<- list.files(diretorio, paste0(PREF,UF,ANO,MESES,".*.dbc")) arquivos
No total, selecionamos 133 arquivos.
$ ls -ltr PASP{10,11,12,13,14,15,16}* | wc
133 1197 8610
$ ls -ltr PASP{10,11,12,13,14,15,16}* | awk '{total += $5}; END {print total}'
1.05298e+10
Como podemos ver, o tamanho dos arquivos é muito maior; os 133 arquivos totalizam aproximadamente 10529800000 bytes, i.e., 10.529.800.000 bytes, ou 10GB.
Como o formato dbc
é um formato comprimido, quando fazemos a leitura cada data.frame fica bem maior.
<- read.dbc(paste0(diretorio,arquivos[1])) f1
object.size(f1)
824834376 bytes
Aproximadamente 800MB.
Em disco, este arquivo tem aproximadamente 88MB:
$ ls -shc PASP1001.dbc
88M PASP1001.dbc
Ou seja, quase 10 vezes maior o tamanho em memória em relação ao tamanho em disco.
Criando uma lista com os nomes completos dos arquivos (com o diretório).
<- lapply(arquivos, function(x) {paste0(diretorio,x)})
fNames <- NULL arqs
<- system.time(arqserial <- lapply(fNames[1:12], read.dbc))
ptimes 3] ptimes[
elapsed
845.107
Realmente, os arquivos do Estado de São Paulo são muito maiores do que dos outros estados.
A função mclapply
é o equivalente paralelo da função lapply
que utilizamos acima. mclapply
faz parte do pacote parallel é muito conveniente para uma paralelização trivial de operações que envolvam listas.
library(parallel)
Vou usar 12 arquivos diferentes para não correr o risco de cache do SO.
<- system.time(arqs <- parallel::mclapply(fNames[13:24], read.dbc, mc.cores = numCores))
ptime 3] ptime[
elapsed
167.033
A leitura em paralelo teve uma redução significativa no tempo; o speedup (\(\frac{temposerial}{tempoparalelo}\)) resulta em 5.06.
Apesar de esta máquina ter 32 cores e uma boa quantidade de memória RAM (128GB), ao tentar executar o mclapply
com todos os 133 arquivos, travou a máquina. Estes 133 arquivos tem um tamanho em disco de quase 10GB; considerando uma razão de 10 vezes o tamanho em memória, chegaríamos a quase 100GB; mais os buffers de leitura, etc., esgotamos a memória da máquina e vamos para o swap.
Assim, é prudente executar em grupos de 10-12 arquivos para não incorrer no uso de swap, o que faz com que a máquina fique praticamente inacessível.
Text and figures are licensed under Creative Commons Attribution CC BY 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".
For attribution, please cite this work as
Menezes (2020, Oct. 2). Tips to Share and Grow: Paralelizando a leitura de muitos arquivos no R. Retrieved from https://momenezes.github.io/tutorials/posts/2020-10-02-paralelizando-a-leitura-de-muitos-arquivos/
BibTeX citation
@misc{menezes2020paralelizando, author = {Menezes, Mario O. de}, title = {Tips to Share and Grow: Paralelizando a leitura de muitos arquivos no R}, url = {https://momenezes.github.io/tutorials/posts/2020-10-02-paralelizando-a-leitura-de-muitos-arquivos/}, year = {2020} }