A placa do Arduino Uno possui RAM limitada, o que significa que possui uma pilha de chamadas limitada disponível. Às vezes, a recursão é a única opção rápida para implementar um determinado algoritmo. Portanto, considerando que a pilha de chamadas é severamente limitada, qual seria a maneira de descobrir que, dado um determinado programa em execução no quadro, exatamente quantas chamadas recursivas você pode pagar antes que haja um estouro de pilha (e coisas ruins acontecem)?
programming
sram
asheeshr
fonte
fonte
How much ca!@#QFSD@$RFW
:? Estou curioso para saber por que ninguém editou isso como algo mais significativo (nos últimos 4 anos).211
vezes (dependendo de muitos fatores) :). Veja minha resposta aqui: arduino.stackexchange.com/a/51098/7727 . @NickGammon, ele finge "xingar", eu acho. É um jogo de palavras para "recursão". Levei um minuto para descobrir isso também. Foi bastante confuso no começo.Respostas:
Se você realmente deseja se retribuir (e como @jippie disse que é uma péssima idéia; mensagem subliminar: não faça isso ) e quer saber o quanto você pode retribuir, será necessário realizar alguns cálculos e experimentos; Além disso, você geralmente terá apenas uma aproximação, pois depende muito do estado da memória no momento em que sua função recursiva será chamada.
Para isso, você deve primeiro saber como a SRAM é organizada no Arduino baseado no AVR (não se aplica, por exemplo, ao Arduino Galileo da Intel). O diagrama a seguir da Adafruit mostra claramente:
Então você precisa saber o tamanho total da sua SRAM (depende do Atmel MCU, portanto, que tipo de placa Arduino você possui).
Neste diagrama, é fácil descobrir esse tamanho do bloco Static Data , pois é conhecido em tempo de compilação e não será alterado posteriormente.
O tamanho da pilha pode ser mais difícil de saber, pois pode variar em tempo de execução, dependendo das alocações de memória dinâmica (
malloc
ounew
) executadas pelo seu esboço ou pelas bibliotecas que ele usa. O uso de memória dinâmica é bastante raro no Arduino, mas algumas funções padrão o fazem (o tipoString
usa, eu acho).Para o tamanho da pilha , ele também varia durante o tempo de execução, com base na profundidade atual das chamadas de função (cada chamada de função ocupa 2 bytes na pilha para armazenar o endereço do chamador) e o número e tamanho das variáveis locais, incluindo argumentos passados ( que também são armazenados na pilha ) para todas as funções chamadas até agora.
Então, vamos supor que sua
recurse()
função use 12 bytes para suas variáveis e argumentos locais, então cada chamada para essa função (a primeira de um chamador externo e a recursiva) usará12+2
bytes.Se supusermos que:
recurse()
função é chamada do esboço, a pilha atual tem 128 bytesEntão você fica com
2048 - 132 - 128 = 1788
os bytes disponíveis na pilha . O número de chamadas recursivas para sua função é1788 / 14 = 127
, portanto , incluindo a chamada inicial (que não é recursiva).Como você pode ver, isso é muito difícil, mas não impossível de encontrar o que você deseja.
Uma maneira mais simples de obter o tamanho da pilha disponível antes da
recurse()
chamada seria usar a seguinte função (encontrada no Adafruit learning center; eu mesmo não testei):É altamente recomendável que você leia este artigo no Adafruit learning center.
fonte
.bss
representa as variáveis globais sem valor inicial no seu código, enquanto quedata
é para variáveis globais com um valor inicial. Mas, no final, eles usam o mesmo espaço: Dados estáticos no diagrama.static
em uma função.A recursão é uma má prática em um microcontrolador, como você já se declarou e provavelmente deseja evitá-la sempre que possível. No site do Arduino, existem alguns exemplos e bibliotecas disponíveis para verificar o tamanho da RAM livre . Você pode, por exemplo, usar isso para descobrir quando interromper a recursão ou um pouco mais complicado / arriscado para criar um perfil do seu esboço e codificar o limite nele. Esse perfil seria necessário para todas as alterações no seu programa e para todas as alterações na cadeia de ferramentas do Arduino.
fonte
Depende da função.
Toda vez que uma função é chamada, um novo quadro é empurrado para a pilha. Geralmente, ele contém vários itens críticos, incluindo potencialmente:
this
) se estiver chamando uma função de membro.Como você pode ver, o espaço de pilha necessário para uma determinada chamada depende da função. Por exemplo, se você escrever uma função recursiva que usa apenas um
int
parâmetro e não usa variáveis locais, ela não precisará de muito mais do que alguns bytes na pilha. Isso significa que você pode chamá-lo recursivamente muito mais do que uma função que usa vários parâmetros e usa muitas variáveis locais (que consumirão a pilha muito mais rapidamente).Obviamente, o estado da pilha depende do que mais está acontecendo no código. Se você iniciar uma recursão diretamente na
loop()
função padrão , provavelmente não haverá muito na pilha. No entanto, se você o iniciar aninhar vários níveis em outras funções, não haverá muito espaço. Isso afetará quantas vezes você pode se recuperar sem esgotar a pilha.Vale a pena notar que a otimização da recursão da cauda existe em alguns compiladores (embora não tenha certeza se o avr-gcc a suporta). Se a chamada recursiva é a última coisa em uma função, significa que às vezes é possível evitar alterar o quadro da pilha. O compilador pode apenas reutilizar o quadro existente, já que a chamada 'pai' (por assim dizer) terminou de usá-lo. Isso significa que, teoricamente, você pode continuar recorrendo o quanto quiser, desde que sua função não chame mais nada.
fonte
Eu tinha exatamente a mesma pergunta que estava lendo Jumping into C ++, de Alex Allain , Capítulo 16: Recursão, p.230, então fiz alguns testes.
TLDR;
Meu Nano do Arduino (ATmega328 mcu) pode fazer 211 chamadas de função recursivas (para o código fornecido abaixo) antes que o estouro da pilha seja interrompido.
Primeiro, deixe-me abordar esta reivindicação:
[Atualização: ah, eu deslizei a palavra "rápido". Nesse caso, você tem alguma validade. No entanto, acho que vale a pena dizer o seguinte.]
Não, não acho que seja uma afirmação verdadeira. Estou certo de que todos os algoritmos têm uma solução recursiva e não recursiva, sem exceção. Às vezes é bem mais fácilpara usar um algoritmo recursivo. Dito isto, a recursão é muito desaprovada para uso em microcontroladores e provavelmente nunca seria permitida em códigos críticos de segurança. No entanto, é possível, é claro, fazê-lo em microcontroladores. Para saber o quão "profundo" você pode entrar em qualquer função recursiva, basta testá-lo! Execute-o em seu aplicativo da vida real em um caso de teste da vida real e remova sua condição de base para que ela se recupere infinitamente. Imprima um contador e veja você mesmo o quão "profundo" você pode ir para saber se o seu algoritmo recursivo está pressionando demais os limites da sua RAM para ser usado na prática. Aqui está um exemplo abaixo para forçar o estouro de pilha em um Arduino.
Agora, algumas notas:
Quantas chamadas recursivas ou "empilhamento de quadros" você pode receber são determinadas por vários fatores, incluindo:
free_RAM = total_RAM - stack_used - heap_used
ou você pode dizerfree_RAM = stack_size_allocated - stack_size_used
)Meus resultados:
Segmentation fault (core dumped)
#pragma GCC optimize ("-O0")
ao topo do arquivo e refaça:Here are the final print results: 209 210 211 ⸮ 9⸮ 3⸮
O código:
A aplicação para PC:
O programa "Sketch" do Arduino:
Referências:
#pragma GCC optimize
comando desde que eu sabia que tinha documentado lá.fonte
#pragma
não usar o que está usando lá. Em vez disso, você pode adicionar__attribute__((optimize("O0")))
à única função que deseja desativar.Eu escrevi este programa de teste simples:
Eu o compilei para o Uno e, enquanto escrevo, ele se repetiu mais de um milhão de vezes! Não sei, mas o compilador pode ter otimizado este programa
fonte
call xxx
/ret
porjmp xxx
. Isso equivale à mesma coisa, exceto que o método do compilador não consome a pilha. Assim, você pode receber bilhões de vezes com seu código (outras coisas são iguais).#pragma GCC optimize ("-O0")
ao topo do seu programa Arduino. Acredito que você precise fazer isso na parte superior de cada arquivo ao qual deseja aplicar - mas eu não procuro isso há anos, então pesquise-o para ter certeza.