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.
Os tipos de dados primitivos usados pela plataforma podem ser resumidos na seguinte tabela:
Observemos que:
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.
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 é 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:
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.
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.
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!
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
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:
const char STR_SRAM[] = "Vou ocupar espaço na SRAM";// 26 bytes desperdiçados
...
lcd.print("Vou ocupar espaço na SRAM"); // 26 bytes desperdiçados
...
const PROGMEM char STR_FLASH[] = "Vou ocupar espaço na FLASH";// 26 bytes economizados
...
lcd.print("Vou ocupar espaço na FLASH"); // 26 bytes economizados
O que fica:
Com certeza, faça uso de PROGMEM e F() para suas constantes.
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:
String StarTrek;
StarTrek.reserve(50); //Este comando evitará a desfragmentação
...
StarTrek = "Espaço, a fronteira final";
StarTrek += "Esta são as viagens da nave estelar Enterprise...";
...
Resumo:
Reserve suas Strings. Mas cuidado para não superdimensionar!
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
Em último caso, quando não há mais o que fazer, podem-se tomar algumas medidas desesperadas:
Veja essas técnicas nas referências abaixo.
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í!
Comentar
Luciano,
#define TEMPO 500 // essa linha não declara qualquer variável e nem aloca espaço na RAM e nem em FLASH
abaixo declaramos uma variável chamada RamVar com o valor 500 (uma mera subtituição, como se fosse um localize->subtitua
int RamVar = TEMPO;
abaixo declaramos uma variável que alocará FLASH, usando antes da declaração do tipo e nome da variável o modificador de tipo "const PROGMEM" - isso é o que faz a variável ficar em Flash!
const PROGMEM int FlashVar = TEMPO/50; // FlashVar terá o valor constante de 10
TEMPO é substituido por 500 em todo lugar ANTES de iniciar a compilação do programa por um processo chamado de PRE-PROCESSADOR.
poderíamos fazer algo como:
#define LARGURA 200
#define CENTRO (LARGURA/2)
int x = CENTRO; // o preprocessador vai subtituir esta linha para int x = 100;
x será uma variável alocada em RAM com 2 bytes por causa do espaço necessário para um tipo <int> -- isso para o processador ATMEL 328.... outros processadores de 32 bits vão alocar 4 bytes para o tipo <int>
Espero que tenha ajudado a entender o que faz o #define e como declarar variáveis em RAM ou variáveis constantes em FLASH, para a qual o seu valor NUNCA muda ao longo da execução do programa.
Abraços.
Alguém poderia confirmar como é o comportamento das variáveis que são declaradas utilizando o #define. Elas ficam na flash ou vão para a Ram desperdiçando recursos?
muito bom, bem escrito
Muito boa a explicação.
Só ajuste o exemplo da utilização da macro F() que a mesma não foi adicionada.
lcd.print("Vou ocupar espaço na FLASH"); // 26 bytes economizados
para
lcd.print(F("Vou ocupar espaço na FLASH")); // 26 bytes economizados
Parabéns
Excelente!
Flavio, bom dia!
Realmente usar define para constantes é melhor do que usar variáveis. Deixa o código organizado. Outra alternativa é usar const. A vantagem é que const é tipado.
Existe uma polêmica em torno de define x const:
http://www.revista-programar.info/artigos/arduino-const-vs-define/
http://forum.arduino.cc/index.php?topic=44023.0
https://www.arduino.cc/en/Reference/Define
Usar o pre-processador para atribuir valores fixos (constantes), ou definição de pinos, em vez de usar int pino 13, por exemplo.
#define PINO13 13
...
pinMode(PINO13, OUTPUT);
...
digitalWrite(PINO13, HIGH);
...
digitalWrite(PINO13, LOW);
Excelente!!!
Bem-vindo a
Laboratorio de Garagem (arduino, eletrônica, robotica, hacking)
© 2024 Criado por Marcelo Rodrigues. Ativado por
Você precisa ser um membro de Laboratorio de Garagem (arduino, eletrônica, robotica, hacking) para adicionar comentários!
Entrar em Laboratorio de Garagem (arduino, eletrônica, robotica, hacking)