Estou usando muita memória RAM. Como isso pode ser medido?


19

Eu gostaria de saber quanta RAM eu estou usando no meu projeto, tanto quanto posso dizer, não há como realmente resolver isso (além de passar por mim e calculá-la). Eu cheguei a um estágio em um projeto bastante grande, onde determinei que estou ficando sem memória RAM.

Eu determinei isso porque posso adicionar uma seção e, em seguida, todo o inferno se solta em outro lugar do meu código sem motivo aparente. Se eu #ifndefalgo mais, funciona novamente. Não há nada programaticamente errado com o novo código.

Suspeitei por um tempo que estava chegando ao fim da RAM disponível. Eu não acho que estou usando muita pilha (embora seja possível), qual é a melhor maneira de determinar quanta RAM eu estou realmente usando?

Passando e tentando resolver isso, tenho problemas quando chego a enumerações e estruturas; quanta memória eles custam?

primeira edição: TAMBÉM editei muito meu esboço desde o início, esses não são os resultados reais que obtive inicialmente, mas são o que estou obtendo agora.

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

A primeira linha (com texto 17554) não estava funcionando. Após muita edição, a segunda linha (com texto 16316) está funcionando como deveria.

edit: a terceira linha tem tudo funcionando, leitura serial, minhas novas funções, etc. Eu essencialmente removi algumas variáveis ​​globais e código duplicado. Menciono isso porque (como se suspeita) não se trata desse código em si, mas do uso da RAM. O que me leva de volta à pergunta original: "como melhor mensurá-la"? Ainda estou consultando algumas respostas, obrigado.

Como realmente interpreto as informações acima?

Até agora, meu entendimento é:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

como o BSS é consideravelmente menor que 1024 bytes, por que o segundo funciona, mas o primeiro não? Se for, DATA+BSSambos ocupam mais de 1024.

reedição: editei a pergunta para incluir o código, mas agora a removi porque realmente não tinha nada a ver com o problema (exceto talvez práticas ruins de codificação, declarações de variáveis ​​e similares). Você pode revisar o código revisando as edições, se realmente quiser vê-lo. Eu queria voltar à pergunta em questão, que era mais baseada em: Como medir o uso da RAM.


Pensei em acrescentar, adicionei várias novas seções de código nas últimas semanas e o otimizei até que funcionasse, mas agora adicionei apenas meio doz byte vars e pronto ... :(
Madivad

Você usa o Stringtipo em seus programas? Sabe-se que ele executa alocações e liberações de memória dinâmica freqüentes, o que pode fragmentar o heap até o ponto em que você não pode ficar com mais memória.
jfpoilpret

@jfpoilpret Fico longe de Strings por causa da sobrecarga. Estou feliz por trabalhar com matrizes de caracteres, isto é, quase sempre defino todas as minhas matrizes de caracteres com um tamanho fixo (no momento, tenho uma matriz de UM byte que não é apenas porque altero o comprimento do conteúdo para diferentes recompilações.
Madivad

A publicação do seu código aqui (ou para colar obin se for muito grande) pode ajudar a descobrir quais problemas você encontra com a memória.
jfpoilpret

@jfpoilpret Eu realmente não posso postar o código, é enorme e infelizmente muito inchado, distribuído por 16 arquivos. Era um projeto que eu permitia crescer muito além do necessário (são vários projetos mesclados). Estou começando agora a desmembrá-lo, o que certamente ajudará a solucionar o problema. Embora existam algumas partes que eu preciso que as pessoas olhem (ou me orientem), eu as postarei mais tarde.
Madivad 17/03/14

Respostas:


15

Você pode usar as funções fornecidas AVRGCC: Monitorando o uso da pilha

A função foi planejada para verificar o uso da pilha, mas o que é relatado é a RAM real que nunca foi usada (durante a execução). Isso é feito "pintando" (preenchendo) a RAM com um valor conhecido (0xC5) e, em seguida, verificando a área da RAM contando quantos bytes ainda têm o mesmo valor inicial.
O relatório mostrará a RAM que não foi usada (mínimo de RAM livre) e, portanto, você poderá calcular o máximo de RAM que foi usado (RAM total - RAM relatada).

Existem duas funções:

  • StackPaint é executado automaticamente durante a inicialização e "pinta" a RAM com o valor 0xC5 (pode ser alterado, se necessário).

  • StackCount pode ser chamado a qualquer momento para contar a RAM que não foi usada.

Aqui está um exemplo de uso. Não faz muito, mas pretende mostrar como usar as funções.

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}

código interessante, obrigado. Eu o usei e sugere que há mais de 600 bytes disponíveis, mas quando enterro isso nos subs mais profundos, ele reduz, mas não se apaga. Então, talvez o meu problema esteja em outro lugar.
Madivad 17/03/14

@Madivad Observe que esses mais de 600 bytes representam a quantidade mínima disponível de RAM até o momento em que você chamou StackCount. Realmente não faz diferença a profundidade com a qual você faz a chamada, se a maioria do código e as chamadas aninhadas foram executadas antes de chamar a StackCount, o resultado estará correto. Portanto, por exemplo, você pode deixar sua placa funcionando por um tempo (o tempo necessário para obter uma cobertura de código suficiente ou, idealmente, até você obter o mau comportamento descrito) e pressionar um botão para obter a RAM relatada. Se houver o suficiente, não será a causa do problema.
alexan_e

11
Obrigado @alexan_e, criei uma área em minha tela que relata isso agora. Assim, à medida que progredir nos próximos dias, observarei esse número com interesse, especialmente quando ele falhar! Obrigado novamente
Madivad

@Madivad Por favor note que a função dada não vai relatar os resultados corretos se malloc () é usado no código
alexan_e

obrigado por isso, estou ciente, já foi mencionado. Tanto quanto sei, não estou usando (sei que pode haver uma biblioteca usando, ainda não verifiquei completamente).
Madivad 20/03/14

10

Os principais problemas que você pode ter com o uso de memória em tempo de execução são:

  • não há memória disponível no heap para alocações dinâmicas ( mallocou new)
  • não há espaço na pilha ao chamar uma função

Ambos são realmente os mesmos que o AVR SRAM (2K no Arduino) é usado para ambos (além de dados estáticos cujo tamanho nunca muda durante a execução do programa).

Geralmente, a alocação dinâmica de memória raramente é usada em MCUs, apenas algumas bibliotecas normalmente a usam (uma delas é a Stringclasse, que você mencionou que não usa, e esse é um bom ponto).

A pilha e a pilha podem ser vistas na figura abaixo (cortesia de Adafruit ): insira a descrição da imagem aqui

Portanto, o problema mais esperado vem do estouro de pilha (ou seja, quando a pilha cresce em direção ao heap e transborda sobre ele e, se o heap não foi usado, transborda na zona de dados estáticos da SRAM. você tem um alto risco de:

  • corrupção de dados (ou seja, a pilha grava dados estáticos ou heap), oferecendo um comportamento incompreensível
  • corrupção de pilha (ou seja, os dados estáticos ou heap sobrescrevem o conteúdo da pilha), geralmente levando a uma falha

Para saber a quantidade de memória que resta entre a parte superior da pilha e a parte superior da pilha (na verdade, podemos chamá-la de parte inferior se representarmos a pilha e a pilha na mesma imagem mostrada abaixo), você pode usar a seguinte função:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

No código acima, __brkvalaponta para o topo do heap, mas é 0quando o heap não foi usado; nesse caso, usamos para &__heap_startonde aponta __heap_start, a primeira variável que marca a parte inferior do heap; &vé claro que está no topo da pilha (esta é a última variável pressionada na pilha); portanto, a fórmula acima retorna a quantidade de memória disponível para a pilha (ou a pilha se você a usar) crescer.

Você pode usar esta função em vários locais do seu código para tentar descobrir onde esse tamanho está ficando drasticamente reduzido.

Obviamente, se você vir esta função retornar um número negativo, será tarde demais: você já ultrapassou a pilha!


11
Para os moderadores: desculpe-me por colocar esta postagem no wiki da comunidade, devo ter cometido algo errado ao digitar, no meio da postagem. Coloque-o novamente aqui, pois essa ação não foi intencional. Obrigado.
jfpoilpret

obrigado por esta resposta, eu literalmente apenas encontrei esse código há apenas uma hora (na parte inferior do playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Ainda não o incluí (mas incluirei), pois tenho uma área bastante grande para depuração no meu monitor. Acho que fiquei confuso sobre a atribuição dinâmica de coisas. É mallocenew a única maneira de fazer isso? Nesse caso, não tenho nada dinâmico. Além disso, eu acabei de aprender que a ONU tem 2K de SRAM. Eu pensei que era 1K. Levando isso em consideração, não estou ficando sem memória RAM! Eu preciso procurar em outro lugar.
Madivad 17/03/14

Além disso, há também calloc. Mas você pode estar usando 3º libs partido que usam alocação dinâmica sem você saber (você teria que verificar o código-fonte de todas as suas dependências para ter certeza sobre isso)
jfpoilpret

2
Interessante. O único "problema" é que ele relata a RAM livre no ponto em que é chamada, portanto, a menos que seja colocada na parte correta, você poderá não perceber uma pilha excedente. A função que forneci parece ter uma vantagem nessa área, pois relata a RAM livre mínima até esse ponto; depois que um endereço de RAM foi usado, ele não é mais relatado como livre (no lado inferior, pode haver alguma RAM ocupada bytes que correspondem ao valor "paint" e são relatados como livres). Além disso, talvez uma maneira seja melhor que a outra, dependendo do que o usuário deseja.
alexan_e

Bom ponto! Eu não havia notado esse ponto específico na sua resposta (e para mim parecia um bug), agora vejo o ponto de "pintar" a zona franca antecipadamente. Talvez você possa deixar esse ponto mais explícito na sua resposta?
Jfpoilpret 20/03/2014

7

Ao descobrir como localizar o arquivo .elf gerado em seu diretório temporário, você pode executar o comando abaixo para despejar um uso da SRAM, onde project.elfdeve ser substituído pelo .elfarquivo gerado . A vantagem dessa saída é a capacidade de inspecionar como sua SRAM é usada. Todas as variáveis ​​precisam ser globais, são realmente todas necessárias?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

Observe que isso não mostra o uso da pilha ou da memória dinâmica, como Ignacio Vazquez-Abrams observou nos comentários abaixo.

Além disso, um avr-objdump -S -j .data project.elf pode ser verificado, mas nenhum dos meus programas produz nada com isso, então não sei ao certo se é útil. Ele deveria listar 'dados inicializados (diferentes de zero)'.


Ou você pode apenas usar avr-size. Mas isso não mostra alocações dinâmicas ou uso de pilha.
Ignacio Vazquez-Abrams

@ IgnacioVazquez-Abrams sobre a dinâmica, o mesmo para a minha solução. Editou minha resposta.
jippie

Ok, esta é a resposta mais interessante até agora. Eu experimentei avr-objdumpe avr-sizeeditarei minha postagem acima em breve. Obrigado por isso.
Madivad 17/03/14

3

Suspeitei por um tempo que estava chegando ao fim da RAM disponível. Eu não acho que estou usando muita pilha (embora seja possível), qual é a melhor maneira de determinar quanta RAM eu estou realmente usando?

Seria melhor usar uma combinação de estimativa manual e usando o sizeofoperador. Se todas as suas declarações forem estáticas, isso deve fornecer uma imagem precisa.

Se você estiver usando alocações dinâmicas, poderá encontrar um problema assim que começar a desalocar a memória. Isso ocorre devido à fragmentação da memória no heap.

Passando e tentando resolver isso, tenho problemas quando chego a enumerações e estruturas; quanta memória eles custam?

Um enum ocupa tanto espaço quanto um int. Portanto, se você tiver um conjunto de 10 elementos em uma enumdeclaração, seria isso 10*sizeof(int). Além disso, toda variável que usa uma enumeração é simplesmente umint .

Para estruturas, seria mais fácil usar sizeofpara descobrir. As estruturas ocupam um espaço (mínimo) igual à soma de seus membros. Se o compilador estruturar o alinhamento, pode ser mais, porém isso é improvável no caso de avr-gcc.


Atribuo estaticamente tudo o mais que posso. Eu nunca pensei em usar sizeofpara esse fim. No momento, já tenho quase 400 bytes (globalmente). Agora vou analisar e calcular as enumerações (manualmente) e as estruturas (das quais tenho algumas - e vou usar sizeof), e relatar.
Madivad 16/03/14

Não tenho certeza de que realmente precisa sizeofsaber o tamanho dos seus dados estáticos, pois isso é impresso pelo avrdude IIRC.
jfpoilpret

@jfpoilpret Isso depende da versão, eu acho. Nem todas as versões e plataformas fornecem isso. O meu (Linux, várias versões) não mostra o uso de memória para um, enquanto as versões do Mac mostram.
Asheeshr # 16/14

Tenho pesquisado na saída detalhada, achei que deveria estar lá, não é
Madivad

@AsheeshR Eu não sabia disso, o meu funciona bem no Windows.
jfpoilpret

1

Existe um programa chamado Arduino Builder que fornece uma visualização clara da quantidade de flash, SRAM e EEPROM que seu programa está usando.

Arduino Builder

O construtor Arduino faz parte do solução IDE CodeBlocks Arduino . Ele pode ser usado como um programa independente ou através do IDE CodeBlocks Arduino.

Infelizmente, o Arduino Builder é um pouco antigo, mas deve funcionar na maioria dos programas e na maioria dos Arduinos, como o Uno.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.