4.19. Pools de memória¶
Uma câmera que mantém três quadros em resolução total em um pool de framebuffer, executa um buffer de pré-visualização separado ao mesmo tempo e ainda tem espaço para um script Python e seus objetos está gerenciando mais memória do que um único bloco de RAM no MCU poderia fornecer. O MicroPython faz tudo caber distribuindo a memória entre os vários tipos distintos de memória que o MCU fornece, e direcionando cada tipo de alocação para o tipo de memória de que ela realmente precisa.
4.19.1. Tipos de memória¶
Um MCU moderno da OpenMV Cam expõe quatro tipos distintos de memória. O primeiro é invisível para a aplicação; os outros três são pools dos quais as alocações podem vir.
O cache de dados da CPU – uma região de memória pequena e muito rápida que fica entre a CPU e o restante da RAM. Quando a CPU lê ou escreve um valor da memória principal, o cache mantém automaticamente uma cópia, de modo que acessos repetidos aos mesmos dados permaneçam no cache e nunca paguem o custo de ir até a memória mais lenta. O cache não é um pool do qual as alocações vêm. Ele é transparente para a aplicação – ele apenas faz com que o restante da RAM pareça mais rápido na prática do que sua latência bruta sugeriria, até o ponto em que um conjunto de trabalho deixa de caber nele.
Memória fortemente acoplada ao processador – um pequeno bloco de RAM ligado diretamente à CPU sem nenhum barramento entre eles. Acesso em ciclo único, nunca falha, nunca espera. Alocações que genuinamente precisam da memória mais rápida possível – onde cada ciclo de latência importa – vêm deste pool.
Memória rápida no chip – de algumas centenas de kilobytes até cerca de um megabyte de RAM, integrada ao encapsulamento do MCU. Baixa latência, alta largura de banda, mas limitada em tamanho. O heap do MicroPython reside aqui para que os acessos a objetos Python permaneçam rápidos; buffers de trabalho menores que a CPU toca com frequência compartilham o pool.
Memória de grande capacidade mais lenta – em placas que combinam o MCU com um chip de memória externo, dezenas de megabytes de RAM fora do chip acessada pelo barramento externo. Muito maior, mas cada acesso leva mais tempo do que a memória no chip; o cache de dados oculta grande parte desse custo para conjuntos de trabalho que ele consegue armazenar, e a diferença aparece em operações que percorrem dados grandes demais para caber no cache. Usada para alocações que precisam ser grandes e que a CPU pode tolerar em velocidade mais baixa – mais importante, o pool de framebuffer.
As placas da família situam-se em um espectro: algumas têm apenas RAM no chip; algumas combinam RAM no chip com um bloco externo muito maior. Cada um dos três tipos alocáveis é tratado como um pool de memória – um bloco do qual as alocações vêm – e rotulado para que cada requisição possa pedir o tipo de memória de que realmente precisa.
4.19.2. O framebuffer primário¶
O framebuffer que dá suporte ao snapshot() não pede memória rápida. Ele pede memória suficiente – nada mais. Isso o coloca no pool que for maior, de modo que, em uma placa com memória no chip e externa, o framebuffer acaba no bloco externo.
Um framebuffer em resolução total e com triplo buffer é grande demais para caber no pool rápido no chip na maioria dos componentes; o pool maior é o único que consegue armazená-lo. O cache de dados da CPU oculta grande parte do custo por acesso quando a aplicação processa a imagem, e o mecanismo de DMA que preenche o framebuffer a partir do sensor acompanha a taxa de dados do sensor de qualquer forma.
O tamanho exato que o framebuffer ocupa é determinado a partir do pixformat(), do framesize() e da contagem de framebuffers() atuais; ele cresce ou encolhe sempre que qualquer um desses muda.
4.19.3. Framebuffers de sensores secundários¶
Uma segunda instância de CSI recebe seu próprio framebuffer, alocado do mesmo pool que o primário usa. O pool é compartilhado; os buffers são independentes. O consumo do secundário normalmente é muito menor que o do primário, porque sensores secundários operam em resoluções mais baixas, então a memória extra que o segundo framebuffer ocupa é uma pequena fração da do primário.
4.19.4. O framebuffer de stream¶
O buffer de pré-visualização de imagem é a exceção. Ele não é alocado de nenhum dos pools em tempo de execução; é uma região fixa reservada em tempo de compilação, com um endereço conhecido e um tamanho conhecido. Isso mantém o caminho de pré-visualização fora do caminho de todas as outras alocações – a região existe desde a inicialização e nunca se move.
4.19.5. O heap do MicroPython¶
Objetos Python – variáveis, listas, dicionários, instâncias de classes, o wrapper Image que uma chamada a snapshot() retorna, cada string e tupla que a aplicação cria – residem no heap com coleta de lixo do MicroPython, que é separado dos pools de memória da câmera. O heap com coleta de lixo (GC) é uma região de memória que o MicroPython gerencia por conta própria: o código Python aloca dele implicitamente toda vez que um objeto é criado, e o MicroPython periodicamente varre o heap e recupera o espaço ocupado por objetos que a aplicação não está mais referenciando, de modo que a aplicação nunca precisa liberar nada manualmente.
Uma região dedicada é reservada para o heap do GC na inicialização, normalmente colocada na memória rápida no chip para que o acesso do Python permaneça rápido, com um transbordamento opcional para o bloco externo maior em placas que precisam de mais folga para grandes estruturas de dados.
O Image retornado por snapshot() é um pequeno objeto wrapper no heap do GC; os dados de pixel subjacentes residem no framebuffer em um dos pools da câmera. Os dois nunca disputam a mesma memória.
4.19.6. Juntando tudo¶
Direcionar cada tipo de alocação para o pool certo – buffers grandes para o pool maior onde cabem, dados sensíveis à latência para os pools mais rápidos, o heap do Python para sua própria região, a pré-visualização para seu espaço reservado – é o que torna possível executar um pipeline de captura em resolução total, um canal de pré-visualização e um script Python não trivial lado a lado em componentes que têm apenas alguns megabytes de memória rápida no total.