Estou processando grandes matrizes 3D, que frequentemente preciso dividir de várias maneiras para fazer uma variedade de análises de dados. Um "cubo" típico pode ter cerca de 100 GB (e provavelmente ficará maior no futuro)
Parece que o formato de arquivo recomendado típico para grandes conjuntos de dados em python é usar HDF5 (h5py ou pytables). Minha pergunta é: há algum benefício de velocidade ou uso de memória em usar HDF5 para armazenar e analisar esses cubos em vez de armazená-los em arquivos binários simples? O HDF5 é mais adequado para dados tabulares, em oposição a grandes matrizes como o que estou trabalhando? Vejo que o HDF5 pode fornecer boa compactação, mas estou mais interessado na velocidade de processamento e em lidar com o estouro de memória.
Freqüentemente, desejo analisar apenas um grande subconjunto do cubo. Uma desvantagem de pytables e h5py é que, ao pegar uma parte do array, sempre recebo de volta um array numpy, usando toda a memória. No entanto, se eu cortar um memmap numpy de um arquivo binário simples, posso obter uma visualização, que mantém os dados no disco. Portanto, parece que posso analisar mais facilmente setores específicos de meus dados sem sobrecarregar minha memória.
Eu explorei pytables e h5py e não vi os benefícios de nenhum deles até agora para o meu propósito.
h5py
é mais adequado para conjuntos de dados como o seu do quepytables
. Além disso,h5py
se não retornar uma matriz numpy em memória. Em vez disso, ele retorna algo que se comporta como um, mas não é carregado na memória (semelhante a umamemmapped
matriz). Estou escrevendo uma resposta mais completa (pode não terminá-la), mas espero que este comentário ajude um pouco enquanto isso.type(cube)
dáh5py._hl.dataset.Dataset
. Enquantotype(cube[0:1,:,:])
dánumpy.ndarray
.Respostas:
Vantagens do HDF5: Organização, flexibilidade, interoperabilidade
Algumas das principais vantagens do HDF5 são sua estrutura hierárquica (semelhante a pastas / arquivos), metadados arbitrários opcionais armazenados com cada item e sua flexibilidade (por exemplo, compressão). Essa estrutura organizacional e armazenamento de metadados podem parecer triviais, mas são muito úteis na prática.
Outra vantagem do HDF é que os conjuntos de dados podem ser de tamanho fixo ou flexível. Portanto, é fácil anexar dados a um grande conjunto de dados sem ter que criar uma nova cópia inteira.
Além disso, HDF5 é um formato padronizado com bibliotecas disponíveis para quase todas as linguagens, portanto, compartilhar seus dados em disco entre, digamos, Matlab, Fortran, R, C e Python é muito fácil com HDF. (Para ser justo, não é muito difícil com uma grande matriz binária também, contanto que você esteja ciente da ordem C vs. F e conheça a forma, o tipo de d, etc. da matriz armazenada.)
Vantagens do HDF para uma grande matriz: E / S mais rápida de uma fatia arbitrária
Assim como o TL / DR: para uma matriz 3D de ~ 8 GB, a leitura de uma fatia "completa" ao longo de qualquer eixo levou cerca de 20 segundos com um conjunto de dados HDF5 fragmentado e 0,3 segundos (melhor caso) a mais de três horas (pior caso) para uma matriz memmapped dos mesmos dados.
Além das coisas listadas acima, há outra grande vantagem em um formato de dados em disco "fragmentado" *, como HDF5: ler uma fatia arbitrária (ênfase em arbitrário) normalmente será muito mais rápido, pois os dados em disco são mais contíguos em média.
*
(HDF5 não precisa ser um formato de dados em partes. Ele suporta fragmentação, mas não exige isso. Na verdade, o padrão para criar um conjunto de dados emh5py
não é fragmentar , se bem me lembro.)Basicamente, seu melhor caso de velocidade de leitura de disco e seu pior caso de velocidade de leitura de disco para uma determinada fatia de seu conjunto de dados será bastante semelhante a um conjunto de dados HDF fragmentado (assumindo que você escolheu um tamanho de bloco razoável ou deixe uma biblioteca escolher um para você). Com uma matriz binária simples, o melhor caso é mais rápido, mas o pior caso é muito pior.
Uma ressalva: se você tiver um SSD, provavelmente não notará uma grande diferença na velocidade de leitura / gravação. Com um disco rígido normal, porém, as leituras sequenciais são muito, muito mais rápidas do que as leituras aleatórias. (ou seja, um disco rígido normal tem muito
seek
tempo.) HDF ainda tem uma vantagem em um SSD, mas é mais devido aos seus outros recursos (por exemplo, metadados, organização, etc) do que devido à velocidade bruta.Em primeiro lugar, para esclarecer a confusão, acessar um
h5py
conjunto de dados retorna um objeto que se comporta de forma bastante semelhante a uma matriz numpy, mas não carrega os dados na memória até que sejam fatiados. (Semelhante ao memmap, mas não idêntico.) Dê uma olhada nah5py
introdução para obter mais informações.Cortar o conjunto de dados carregará um subconjunto dos dados na memória, mas provavelmente você deseja fazer algo com ele e, nesse ponto, precisará dele na memória de qualquer maneira.
Se você deseja fazer cálculos fora do núcleo, pode facilmente obter dados tabulares com
pandas
oupytables
. É possível comh5py
(mais agradável para grandes matrizes ND), mas você precisa descer para um nível ligeiramente inferior e lidar com a iteração você mesmo.No entanto, o futuro das computações fora do núcleo parecidas com entorpecentes é Blaze. Dê uma olhada se você realmente deseja seguir esse caminho.
O caso "não selecionado"
Em primeiro lugar, considere uma matriz 3D C ordenada escrita no disco (vou simular chamando
arr.ravel()
e imprimindo o resultado, para tornar as coisas mais visíveis):Os valores seriam armazenados no disco sequencialmente, conforme mostrado na linha 4 abaixo. (Vamos ignorar os detalhes e a fragmentação do sistema de arquivos por enquanto.)
Na melhor das hipóteses, vamos fazer uma fatia ao longo do primeiro eixo. Observe que esses são apenas os primeiros 36 valores da matriz. Esta será uma leitura muito rápida! (uma busca, uma leitura)
Da mesma forma, a próxima fatia ao longo do primeiro eixo terá apenas os próximos 36 valores. Para ler uma fatia completa ao longo deste eixo, precisamos apenas de uma
seek
operação. Se tudo o que vamos ler são várias fatias ao longo deste eixo, essa é a estrutura de arquivo perfeita.No entanto, vamos considerar o pior cenário: uma fatia ao longo do último eixo.
Para ler esta fatia, precisamos de 36 buscas e 36 leituras, pois todos os valores estão separados no disco. Nenhum deles é adjacente!
Isso pode parecer muito pequeno, mas à medida que chegamos a matrizes cada vez maiores, o número e o tamanho das
seek
operações aumentam rapidamente. Para um array 3D grande (~ 10 Gb) armazenado dessa maneira e lido viamemmap
, ler uma fatia inteira ao longo do "pior" eixo pode facilmente levar dezenas de minutos, mesmo com hardware moderno. Ao mesmo tempo, uma fatia ao longo do melhor eixo pode levar menos de um segundo. Para simplificar, estou mostrando apenas fatias "completas" ao longo de um único eixo, mas exatamente a mesma coisa acontece com fatias arbitrárias de qualquer subconjunto de dados.Aliás, existem vários formatos de arquivo que tiram vantagem disso e basicamente armazenam três cópias de enormes arrays 3D no disco: um na ordem C, um na ordem F e um intermediário entre os dois. (Um exemplo disso é o formato D3D do Geoprobe, embora eu não tenha certeza se ele está documentado em qualquer lugar.) Quem se importa se o tamanho final do arquivo é 4 TB, o armazenamento é barato! O mais louco disso tudo é que, como o caso de uso principal é extrair uma única sub-fatia em cada direção, as leituras que você deseja fazer são muito, muito rápidas. Funciona muito bem!
O caso simples "fragmentado"
Digamos que armazenemos "pedaços" 2x2x2 do array 3D como blocos contíguos no disco. Em outras palavras, algo como:
Portanto, os dados no disco seriam
chunked
:E apenas para mostrar que eles são blocos 2x2x2 de
arr
, observe que estes são os primeiros 8 valores dechunked
:Para ler em qualquer fatia ao longo de um eixo, leríamos em 6 ou 9 blocos contíguos (o dobro de dados de que precisaríamos) e então manteríamos apenas a parte que desejávamos. Isso é um máximo de pior caso de 9 buscas contra um máximo de 36 buscas para a versão não fragmentada. (Mas o melhor caso ainda é 6 buscas versus 1 para o array mapeado por mem.) Como as leituras sequenciais são muito rápidas em comparação com as buscas, isso reduz significativamente o tempo que leva para ler um subconjunto arbitrário na memória. Mais uma vez, esse efeito se torna maior com matrizes maiores.
HDF5 leva isso alguns passos adiante. Os pedaços não precisam ser armazenados de forma contígua e são indexados por uma B-Tree. Além disso, eles não precisam ter o mesmo tamanho no disco, então a compressão pode ser aplicada a cada pedaço.
Matrizes fragmentadas com
h5py
Por padrão,
h5py
não cria arquivos HDF fragmentados no disco (acho quepytables
sim, em contraste). Se você especificarchunks=True
ao criar o conjunto de dados, no entanto, obterá uma matriz fragmentada no disco.Como um exemplo rápido e mínimo:
Observe que
chunks=True
dizh5py
para escolher automaticamente um tamanho de bloco para nós. Se você sabe mais sobre seu caso de uso mais comum, pode otimizar o tamanho / forma do bloco especificando uma tupla de formato (por exemplo,(2,2,2)
no exemplo simples acima). Isso permite que você torne as leituras ao longo de um eixo específico mais eficiente ou otimize leituras / gravações de um determinado tamanho.Comparação de desempenho de E / S
Apenas para enfatizar o ponto, vamos comparar a leitura em fatias de um conjunto de dados HDF5 fragmentado e um grande array 3D ordenado por Fortran (~ 8GB) contendo os mesmos dados exatos.
Eu já limpou todos os caches OS entre cada corrida, por isso estamos vendo o desempenho "frio".
Para cada tipo de arquivo, testaremos a leitura em um corte x "completo" ao longo do primeiro eixo e um corte z "completo" ao longo do último eixo. Para a matriz memmapped ordenada por Fortran, a fatia "x" é o pior caso, e a fatia "z" é o melhor caso.
O código usado está em uma essência (incluindo a criação do
hdf
arquivo). Não consigo compartilhar facilmente os dados usados aqui, mas você pode simular isso por uma matriz de zeros da mesma forma (621, 4991, 2600)
e tiponp.uint8
.A
chunked_hdf.py
aparência é assim:memmapped_array.py
é semelhante, mas tem um pouco mais de complexidade para garantir que as fatias sejam realmente carregadas na memória (por padrão, outromemmapped
array seria retornado, o que não seria uma comparação maçãs com maçãs).Vamos dar uma olhada no desempenho do HDF primeiro:
Uma fatia x "completa" e uma fatia z "cheia" levam aproximadamente a mesma quantidade de tempo (~ 20 segundos). Considerando que este é um array de 8 GB, isso não é tão ruim. A maior parte do tempo
E se compararmos isso com os tempos da matriz mapeada por mem (é ordenada por Fortran: uma "fatia z" é o melhor caso e uma "fatia x" é o pior caso.):
Sim, você leu certo. 0,3 segundos para uma direção de corte e ~ 3,5 horas para a outra.
O tempo para fatiar na direção "x" é muito maior do que o tempo que levaria para carregar todo o array de 8 GB na memória e selecionar a fatia que queríamos! (Novamente, esta é uma matriz ordenada por Fortran. O tempo de fatia x / z oposto seria o caso para uma matriz ordenada por C.)
No entanto, se sempre quisermos obter uma fatia ao longo da direção do melhor caso, o grande array binário no disco é muito bom. (~ 0,3 seg!)
Com uma matriz memmapped, você está preso a esta discrepância de E / S (ou talvez anisotropia seja um termo melhor). No entanto, com um conjunto de dados HDF fragmentado, você pode escolher o tamanho do fragmento de forma que o acesso seja igual ou otimizado para um caso de uso específico. Isso dá a você muito mais flexibilidade.
Em suma
Espero que isso ajude a esclarecer uma parte de sua pergunta, de qualquer forma. HDF5 tem muitas outras vantagens sobre os memmaps "brutos", mas não tenho espaço para expandir todas elas aqui. A compactação pode acelerar algumas coisas (os dados com os quais trabalho não se beneficiam muito da compactação, então raramente os uso), e o armazenamento em cache no nível do sistema operacional geralmente funciona mais bem com arquivos HDF5 do que com memmaps "brutos". Além disso, HDF5 é um formato de contêiner realmente fantástico. Ele oferece muita flexibilidade no gerenciamento de seus dados e pode ser usado em mais ou menos qualquer linguagem de programação.
De modo geral, experimente e veja se funciona bem para o seu caso de uso. Eu acho que você pode se surpreender.
fonte