Pular para o conteúdo

    Capítulo 33, Avançado

    Arquivos maiores que a memória: memmap

    Quando o conjunto de dados não cabe na memória, você não precisa carregá-lo. O `memmap` trata um arquivo em disco como um array, e o sistema operacional traz para a memória só o pedaço que você toca.

    Código deste capítulo: avancado/cap33_memmap.py

    Um arquivo que se comporta como array

    O np.memmap mapeia um arquivo binário na memória. Você escreve e lê como em um array comum, e o sistema operacional cuida de transferir as páginas do disco sob demanda. Aqui, um arquivo com um milhão de float32:

    avancado/cap33_memmap.pylinhas 10 a 19
    from pathlib import Path
    
    import numpy as np
    
    caminho = Path("grande.bin")
    mm = np.memmap(caminho, dtype=np.float32, mode="w+", shape=(1_000_000,))
    mm[:] = np.arange(1_000_000, dtype=np.float32)
    mm.flush()
    print(caminho.stat().st_size, mm.shape)
    del mm
    
    Saída
    4000000 (1000000,)
    

    O arquivo tem exatamente 1.000.000 × 4 bytes: o memmap é só os dados, sem cabeçalho, e por isso você precisa informar o tipo e a forma ao abri-lo de novo. O flush garante que as alterações cheguem ao disco, e o del solta o arquivo.

    Abrir só para ler

    Os modos mais usados são "r" (somente leitura), "r+" (leitura e escrita em um arquivo existente) e "w+" (cria ou apaga o arquivo e abre para escrita). No modo de leitura, o array é protegido contra alterações acidentais:

    avancado/cap33_memmap.pylinhas 24 a 29
    lido = np.memmap(caminho, dtype=np.float32, mode="r", shape=(1_000_000,))
    print(lido[999_999], lido[:3], isinstance(lido, np.ndarray))
    try:
        lido[0] = 1.0
    except ValueError as erro:
        print(erro)
    
    Saída
    999999.0 [0. 1. 2.] True
    assignment destination is read-only
    

    Um memmap é um ndarray, e por isso aceita tudo o que você já aprendeu: fatias, máscaras, agregações.

    Processar em blocos

    Mesmo mapeado, um cálculo sobre o array inteiro pode precisar de memória para os intermediários. A solução é percorrer o arquivo em blocos, acumulando o resultado. Aqui, a média de um milhão de valores com no máximo 250 mil em memória por vez:

    avancado/cap33_memmap.pylinhas 34 a 41
    def media_em_blocos(mm, bloco=250_000):
        total = 0.0
        for inicio in range(0, len(mm), bloco):
            total += float(mm[inicio : inicio + bloco].sum(dtype=np.float64))
        return total / len(mm)
    
    
    print(media_em_blocos(lido), np.arange(1_000_000, dtype=np.float64).mean())
    
    Saída
    499999.5 499999.5
    

    O sum(dtype=np.float64) acumula em precisão dupla, mesmo que os dados sejam float32, para o erro de arredondamento não crescer com o tamanho.

    `np.load` com `mmap_mode`

    Um arquivo .npy (capítulo 24) já carrega o tipo e a forma no cabeçalho. Com mmap_mode="r", o np.load o abre como memmap sem você repetir esses dados:

    avancado/cap33_memmap.pylinhas 46 a 48
    np.save("grande.npy", np.arange(1_000_000, dtype=np.float32))
    vista = np.load("grande.npy", mmap_mode="r")
    print(type(vista).__name__, vista[10])
    
    Saída
    memmap 10.0
    

    Esse costuma ser o jeito mais prático: guarde em .npy e abra com mmap_mode.

    Limpeza

    Um memmap mantém o arquivo aberto. No Windows, não dá para apagar um arquivo enquanto algum array ainda o mapeia, então solte as referências antes:

    avancado/cap33_memmap.pylinhas 53 a 56
    del lido, vista
    for nome in ("grande.bin", "grande.npy"):
        Path(nome).unlink()
    print(Path("grande.bin").exists())
    
    Saída
    False
    
    Quando usar memmapQuando não
    Arquivo maior que a memóriaO dado cabe com folga: carregue de uma vez, é mais rápido
    Acesso a pedaços (uma janela, algumas linhas)Você vai ler tudo muitas vezes: o disco é bem mais lento que a memória
    Vários processos lendo o mesmo dadoDados em formato de tabela: use Parquet com pandas

    Exercício 1

    Máximo de um arquivo, em blocos

    Escreva maximo_em_blocos(caminho, dtype, tamanho, bloco) que abra o arquivo como memmap somente leitura e devolva o maior valor, lendo bloco elementos por vez. Teste gravando um arquivo temporário.