Arduino e Otimização da Memória

Dicas para redução da utilização da memória – Técnicas de otimização em sketches Arduino

Quem desenvolve programas para dispositivos com memória reduzida precisa estar antenado com as técnicas de otimização de modo a evitar problemas de performance e estabilidade.
Neste artigo, reunimos importantes dicas que você pode utilizar em seus sketchs com o objetivo de economizar espaço em memória

Antes de explorarmos as técnicas aqui descritas veremos alguns conceitos básicos sobre variáveis, tipos de dados e organização da memória na plataforma Arduino.

Tipos de Dados

Os tipos de dados primitivos usados pela plataforma podem ser resumidos na seguinte tabela:

Tipos de Dados no Arduino
Tipos de Dados no Arduino (Fonte: Adafruit)

Observemos que:

  • Os tipos inteiros podem ser definidos com ou sem sinal. Dessa forma, unsigned long e uint32_t são equivalentes;
  • Os tipos double e float possuem a mesma precisão e ocupam o mesmo espaço na memória;
  • int e short são equivalentes.

Escopo de Variáveis

O escopo das variáveis determina sua visibilidade e tempo de vida dentro de um programa. A linguagem do Arduino define três tipos de escopo:

Global → As variáveis globais podem ser vistas (acessadas) em qualquer ponto do programa (incluindo todas a funções) e permanecem “vivas” durante todo o tempo de execução.
Para definir uma variável como global, simplesmente declare-a fora de qualquer função (antes do setup).

Local → Variáveis locais pode ser acessadas somente dentro da função em que foram definidas e seu tempo de vida termina quando a função termina.
Variáveis locais são declaradas dentro de funções.

Estático → Variáveis estáticas podem ser acessadas somente dentro da função em que foram definidas, mas seu tempo de vida é global, ou sejam, seu valor é preservado entre as chamadas à função.
Variáveis estáticas são declaradas com o modificador static.

Tipos de Memória

Os processadores da linha Atmega 328 (Arduino) adotam o modelo de Harvard (em oposição ao modelo Von Neumann) cuja arquitetura separa fisicamente a memória utilizada pelo programa da usada pelos dados (variáveis), de acordo com a seguinte disposição:

Memória Flash → Memória não volátil onde fica armazenado o programa (sketch). Você pode armazenar dados nesta memória, mas não pode alterá-los.

SRAM → Memória de leitura e escrita usada para armazenamentos dos dados dos programas (variáveis).

EEPROM → Memória não volátil onde podem ser armazenados e lidos dados byte a byte.

A memória SRAM

A memória SRAM é importante, pois é onde se passa toda a ação: Alocação estática e dinâmica de variáveis, ponteiros para chamadas de funções, etc.
É essa parte da memória que devemos concentrar nossos esforços de otimização, pois é aí que acontecem os problemas.

Vejamos como ela está dividida observando esse gráfico:

Constituição da RAM do Arduino
Constituição da RAM do Arduino (Fonte: Adafruit)

Static Data → É onde ficam armazenadas as variáveis globais e estáticas e seu tamanho não varia.

Stack → Memória de tamanho variável ocupada por Ponteiros para chamadas de funções e interrupções, variáveis locais, etc.

Heap → Alocação dinâmica de variáveis, principalmente variáveis de instância.

Os problemas acontecem porque o heap e o Stack podem crescer e ocupar a memória livre até o momento em que ela se esgota, causando erros imprevisíveis e comportamento errático.

Dicas de Otimização

Vejamos agora algumas técnicas de programação e boas práticas que podem ser utilizadas com o objetivo de economizar memória em seus sketchs.

1) Global é do mal…

Embora a maioria dos sketchs que você vê por aí façam uso indiscriminado desse tipo de variável, essa prática não é aconselhada. As variáveis globais ficam armazenadas na seção static data da SRAM e ficam lá “eternamente” ocupando espaço.

Crie funções para as partes repetitivas do código. Programe orientado às funções. Prefira, sempre que possível, usar variáveis locais. Caso necessite que a variável seja acessada em vários pontos do programa, passe-as como parâmetro.

Moral da História:
Pense globalmente. Aloque localmente!

2) Alocação dinâmica x estática

Variáveis locais são armazenados na Stack e seu espaço é liberado quando termina seu escopo. Já, as variáveis alocadas dinamicamente (malloc, calloc) ocupam o heap e nem sempre seu espaço é recuperado, podendo causar a fragmentação dessa região da memória.

Portanto:
Evite a alocação dinâmica da memória

3) Constantes, PROGMEM e a macro F()

Os valores constantes são armazenados na memória FLASH, mas copiados para a memória SRAM ocupando precioso espaço com valores que nunca serão alterados.  O mesmo acontece com constantes Strings literais.

Para evitar esse desperdício de memória, podemos fazer uso da diretiva PROGMEM e a macro F()

PROGMEM é um modificador que instrui ao compilador a armazenar a constante na memória flash. O mesmo pode ser feito com Strings literais, através da macro F().

Veja alguns exemplos:

O que fica:
Com certeza, faça uso de PROGMEM e F() para suas constantes.

4) Variáveis String

O espaço ocupado pelas variáveis da classe String é alocado dinamicamente no heap, o que pode causar sua fragmentação devido principalmente às operações de concatenação. É possível evitar isso, reservando o espaço antes de executar as operações.

Exemplo:

Resumo:
Reserve suas Strings. Mas cuidado para não superdimensionar!

5) Selecionando os Tipos de Dados

Estude os tipos de dados disponíveis na plataforma e escolha aquele que vai ocupar menos espaço.

Por exemplo:
Ao invés de fazer:
int idade = 18;
Prefira:
byte idade = 18;

Outro ponto é evitar criar variáveis indiscriminadamente. Verifique se todas as variáveis estão sendo usadas.

Em suma:
Seja criterioso na definição de variáveis

6) Medidas extremas

Em último caso, quando não há mais o que fazer, podem-se tomar algumas medidas desesperadas:

  • Armazenamento de variáveis na EEPROM
  • Eliminação do bootloader

Veja essas técnicas nas referências abaixo.

Conclusão

When Heap meets Stack. There is all the danger.
De acordo com o que vimos, para programarmos de forma eficiente na plataforma Arduino, é necessário estudar a estrutura dos dados, definir de forma criteriosa sua configuração e desenvolver o código de forma organizada.

Conhece mais alguma dica de economizar memória no Arduino? Comente aí!

Saiba mais…

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *