Carregando matrizes NumPy do disco: Comparando memmap () e Zarr / HDF5

Se sua matriz NumPy for muito grande para caber na RAM, você poderá processá-la dividindo-a em fragmentos . Você pode fazer isso no modo transparente ou explicitamente carregando esses fragmentos do disco, um de cada vez. Nessa situação, você pode recorrer a duas classes de ferramentas:





  • O método NumPy memmap(), um mecanismo transparente que permite perceber um arquivo localizado em um disco como se estivesse na memória. 
  • Formatos de armazenamento de dados Zarr e HDF5 que são semelhantes entre si, que permitem, se necessário, carregar do disco e salvar fragmentos compactados da matriz em disco.

Cada um desses métodos tem seus próprios pontos fortes e fracos. 

O material, cuja tradução publicamos hoje, é dedicado à análise dos recursos desses métodos de trabalho com dados e à história de quais situações eles podem ser úteis. Em particular, será dada atenção especial aos formatos de dados otimizados para a execução de cálculos e não necessariamente projetados para transferir esses dados para outros programadores.

O que acontece ao ler dados de um disco ou gravar dados em um disco?


Quando um arquivo é lido do disco pela primeira vez, o sistema operacional não apenas copia os dados para a memória do processo. Primeiro, ele copia esses dados em sua memória, armazenando uma cópia deles no chamado "cache de buffer".

Qual é a utilidade aqui?

O fato é que o sistema operacional armazena dados no cache, caso você precise ler os mesmos dados do mesmo arquivo novamente.


Se os dados forem lidos novamente, eles entrarão na memória do programa não do disco, mas da RAM, que é uma ordem de magnitude mais rápida.


Se a memória ocupada pelo cache for necessária para outra coisa, o cache será limpo automaticamente.

Quando os dados são gravados no disco, eles se movem na direção oposta. No início, eles são gravados apenas no cache do buffer. Isso significa que as operações de gravação geralmente são muito rápidas, pois o programa não precisa se concentrar em um disco lento. Ela, durante a gravação, precisa trabalhar apenas com RAM.


Como resultado, os dados são liberados para o disco do cache.


Trabalhando com uma matriz usando o memmap ()


No nosso caso, memmap()nos permite perceber um arquivo no disco como se fosse um array armazenado na memória. O sistema operacional, transparente ao programa, executa operações de leitura / gravação, acessando o cache do buffer ou o disco rígido, dependendo de os dados solicitados serem armazenados em cache na memória ou não. Um algoritmo como este é executado aqui:

  • Os dados estão no cache? Se assim for - ótimo - você pode contatá-los diretamente.
  • Os dados estão em disco? O acesso a eles será mais lento, mas você não precisará se preocupar com isso, eles serão carregados no modo transparente.

Como uma vantagem adicional, memmap()pode-se observar que, na maioria dos casos, o cache do buffer do arquivo será incorporado na memória do programa. Isso significa que o sistema não precisa manter uma cópia adicional dos dados na memória do programa fora do buffer.


O método memmap()está embutido no NumPy:

import numpy as np
array = np.memmap("mydata/myarray.arr", mode="r",
                  dtype=np.int16, shape=(1024, 1024))

Execute esse código e você terá uma matriz à sua disposição, cujo trabalho será completamente transparente para o programa - independentemente de o trabalho ser feito com o cache do buffer ou com o disco rígido.

Limitações do Memmap ()


Embora em certas situações ele memmap()possa se mostrar muito bem, esse método também tem limitações:

  • Os dados devem ser armazenados no sistema de arquivos. Os dados não podem ser baixados do armazenamento binário como o AWS S3.
  • , . , . , , , .
  • N- , , , . .

Vamos explicar o último ponto. Imagine que temos uma matriz bidimensional contendo números inteiros de 32 bits (4 bytes). 4096 bytes são lidos por disco. Se você ler dados localizados em um arquivo seqüencialmente em um disco (digamos, esses dados estiverem em linhas de matriz), depois de cada operação de leitura, teremos 1024 números inteiros. Mas se você ler dados cuja localização no arquivo não corresponde à sua localização na matriz (por exemplo, dados localizados em colunas), cada operação de leitura permitirá obter apenas 1 número necessário. Como resultado, acontece que, para obter a mesma quantidade de dados, é necessário executar mil vezes mais operações de leitura.

Zarr e HDF5


Para superar as limitações acima, você pode usar os formatos de armazenamento de dados Zarr ou HDF5, que são muito semelhantes:

  • Você pode trabalhar com arquivos HDF5 no Python usando pytables ou h5py . Este formato é mais antigo que o Zarr e tem mais restrições, mas o seu diferencial é que ele pode ser usado em programas escritos em diferentes idiomas.
  • Zarr é um formato implementado usando o pacote Python com o mesmo nome. É muito mais moderno e flexível que o HDF5, mas você pode usá-lo (pelo menos por enquanto) apenas no ambiente Python. De acordo com meus sentimentos, na maioria das situações, se não houver necessidade de suporte multilíngue ao HDF5, vale a pena escolher o Zarr. O Zarr, por exemplo, tem melhor suporte a multithreading.

Além disso, discutiremos apenas Zarr, mas se você estiver interessado no formato HDF5 e sua comparação mais profunda com Zarr, poderá assistir a este vídeo.

Usando Zarr


O Zarr permite armazenar partes de dados e carregá-las na memória na forma de matrizes, e também - escrever essas partes de dados na forma de matrizes.

Veja como carregar uma matriz usando o Zarr:

>>> import zarr, numpy as np
>>> z = zarr.open('example.zarr', mode='a',
...               shape=(1024, 1024),
...               chunks=(512,512), dtype=np.int16)
>>> type(z)
<class 'zarr.core.Array'>
>>> type(z[100:200])
<class 'numpy.ndarray'>

Observe que, até que uma fatia do objeto seja recebida, não estaremos à nossa disposição numpy.ndarray. Uma entidade zarr.core.arraysão apenas metadados. Somente os dados incluídos na fatia são carregados do disco.

Por que eu escolhi o Zarr?


  • Zarr contorna as limitações memmap()discutidas acima:
  • Fragmentos de dados podem ser armazenados em disco, no armazenamento do AWS S3 ou em algum sistema de armazenamento que fornece a capacidade de trabalhar com registros de formato de chave / valor.
  • O tamanho e a estrutura do fragmento de dados são determinados pelo programador. Por exemplo, os dados podem ser organizados de forma a serem capazes de ler com eficiência as informações localizadas em diferentes eixos de uma matriz multidimensional. Isso é verdade para o HDF5.
  • Fragmentos podem ser compactados. O mesmo pode ser dito do HDF5.

Vamos nos concentrar nos dois últimos pontos em mais detalhes.

Dimensões do fragmento


Suponha que estamos trabalhando com uma matriz de 30.000 x 3.000 elementos em tamanho. Se você precisar ler essa matriz e mover-se ao longo de seu eixo X, movendo-se ao longo de seu eixo Y, poderá salvar fragmentos contendo os dados dessa matriz, conforme mostrado abaixo (na prática, provavelmente, você precisará de mais de 9 fragmentos):


Agora, os dados localizados no eixo Xe no eixo Ypodem ser carregados com eficiência. Dependendo do tipo de dado necessário no programa, é possível fazer o download, por exemplo, dos fragmentos (1, 0), (1, 1), (1, 2) ou fragmentos (0, 1), (1, 1), (2, 1)

Compressão de dados


Cada fragmento pode ser compactado. Isso significa que os dados podem entrar no programa mais rapidamente do que o disco permite ler informações não compactadas. Se os dados forem compactados três vezes, isso significa que eles podem ser baixados do disco três vezes mais rápido que os dados não compactados, menos o tempo que o processador leva para descompactá-lo.


Após o download dos fragmentos, eles podem ser removidos da memória do programa.

Resumo: memmap () ou Zarr?


memmap()Qual é o melhor para usar - ou Zarr?

Memmap()Parece interessante nesses casos:

  • Existem muitos processos que lêem partes do mesmo arquivo. Esses processos, graças ao aplicativo memmap(), poderão compartilhar o mesmo cache de buffer. Isso significa que apenas uma cópia dos dados precisa ser mantida na memória, não importa quantos processos estejam em execução.
  • O desenvolvedor não deseja gerenciar manualmente a memória. Ele planeja simplesmente confiar nos recursos do sistema operacional, que resolverá todos os problemas de gerenciamento de memória de forma automática e invisível para o desenvolvedor.

O Zarr é especialmente útil nas seguintes situações (em algumas delas, como será observado, o formato HDF5 também é aplicável):

  • Os dados são baixados de fontes remotas, não do sistema de arquivos local.
  • É muito provável que o gargalo do sistema esteja lendo do disco. A compactação de dados permitirá o uso mais eficiente dos recursos de hardware. Isso também se aplica ao HDF5.
  • Se você precisar obter fatias de matrizes multidimensionais em diferentes eixos, o Zarr ajudará a otimizar essas operações, selecionando o tamanho e a estrutura apropriados dos fragmentos. Isso é verdade para o HDF5.

Eu escolheria o memmap()Zarr e, antes de tudo, tentaria usar o Zarr - devido à flexibilidade que este pacote oferece e ao formato de armazenamento de dados que ele implementa.

Queridos leitores! Como você resolve o problema de trabalhar com matrizes NumPy grandes?


All Articles