La placa Arduino Uno tiene RAM limitada, lo que significa que tiene una pila de llamadas limitada disponible. A veces, la recursión es la única opción rápida para implementar un cierto algoritmo. Entonces, dado que la pila de llamadas está severamente limitada, ¿cuál sería una manera de descubrir que dado un cierto programa que se ejecuta en el tablero, exactamente cuántas llamadas recursivas puede pagar antes de que se produzca un desbordamiento de la pila (y suceden cosas malas)?
programming
sram
Asheeshr
fuente
fuente
How much ca!@#QFSD@$RFW
:? Tengo curiosidad por saber por qué nadie lo ha editado para que sea algo más significativo (en los últimos 4 años).211
veces (dependiendo de muchos factores) :). Vea mi respuesta aquí: arduino.stackexchange.com/a/51098/7727 . @NickGammon, creo que está fingiendo "maldiciendo". Es un juego de palabras para "recurse". Me tomó un minuto darme cuenta también. Fue bastante confuso al principio.Respuestas:
Si realmente desea recurrir (y como @jippie dijo que es una mala idea; mensaje subliminal: no lo haga ) y desea saber cuánto puede repetir, entonces tendrá que realizar algunos cálculos y experimentos; Además, generalmente solo tendrá una aproximación, ya que depende mucho del estado de la memoria en el momento en que se llamará a su función recursiva.
Para esto, primero debe saber cómo se organiza SRAM dentro de Arduino basado en AVR (no se aplicará, por ejemplo, al Arduino Galileo de Intel). El siguiente diagrama de Adafruit lo muestra claramente:
Entonces necesita saber el tamaño total de su SRAM (depende de Atmel MCU, de ahí qué tipo de placa Arduino tiene).
En este diagrama, es fácil descubrir el tamaño del bloque de datos estáticos , ya que se conoce en tiempo de compilación y no cambiará más adelante.
El tamaño del montón puede ser más difícil de conocer ya que puede variar en tiempo de ejecución, dependiendo de las asignaciones de memoria dinámica (
malloc
onew
) realizadas por su boceto o las bibliotecas que utiliza. Usar memoria dinámica es bastante raro en Arduino, pero algunas funciones estándar lo hacen (el tipo loString
usa, creo).Para el tamaño de la pila , también variará durante el tiempo de ejecución, en función de la profundidad actual de las llamadas a funciones (cada llamada a la función toma 2 bytes en la pila para almacenar la dirección de la persona que llama) y el número y el tamaño de las variables locales, incluidos los argumentos pasados ( que también se almacenan en la Pila ) para todas las funciones llamadas hasta ahora.
Supongamos que su
recurse()
función usa 12 bytes para sus variables y argumentos locales, luego cada llamada a esta función (la primera de un llamador externo y las recursivas) usará12+2
bytes.Si suponemos que:
recurse()
se llama a su función desde su boceto, la pila actual tiene una longitud de 128 bytesLuego te quedan
2048 - 132 - 128 = 1788
bytes disponibles en la Pila . El número de llamadas recursivas a su función es1788 / 14 = 127
, por lo tanto , incluida la llamada inicial (que no es recursiva).Como puede ver, esto es muy difícil, pero no imposible de encontrar lo que desea.
Una forma más simple de obtener el tamaño de pila disponible antes de
recurse()
llamar es utilizar la siguiente función (que se encuentra en el centro de aprendizaje Adafruit; no lo he probado yo mismo):Le recomiendo que lea este artículo en el centro de aprendizaje Adafruit.
fuente
.bss
representa las variables globales sin valor inicial en su código, mientras quedata
es para las variables globales con un valor inicial. Pero al final usan el mismo espacio: datos estáticos en el diagrama.static
dentro de una función.La recursión es una mala práctica en un microcontrolador, ya que usted ya lo dijo y probablemente quiera evitarlo siempre que sea posible. En el sitio de Arduino hay algunos ejemplos y bibliotecas disponibles para verificar el tamaño de RAM libre . Por ejemplo, puede usar esto para averiguar cuándo romper la recursividad o un poco más complicado / arriesgado para perfilar su boceto y codificar el límite en él. Este perfil sería necesario para cada cambio en su programa y para cada cambio en la cadena de herramientas Arduino.
fuente
Depende de la función.
Cada vez que se llama a una función, se empuja un nuevo marco a la pila. Por lo general, contendrá varios elementos críticos, que pueden incluir:
this
) si se llama a una función miembro.Como puede ver, el espacio de pila requerido para una llamada determinada depende de la función. Por ejemplo, si escribe una función recursiva que solo toma un
int
parámetro y no utiliza variables locales, no necesitará mucho más que unos pocos bytes en la pila. Eso significa que puede llamarlo recursivamente mucho más que una función que toma varios parámetros y utiliza muchas variables locales (que consumirán la pila mucho más rápido).Obviamente, el estado de la pila depende de qué más está sucediendo en el código. Si comienza una recursión directamente dentro de la
loop()
función estándar , entonces probablemente ya no habrá mucho en la pila. Sin embargo, si comienza anidando varios niveles en otras funciones, entonces no habrá tanto espacio. Eso afectará cuántas veces puede repetirse sin agotar la pila.Vale la pena señalar que la optimización de recursión de cola existe en algunos compiladores (aunque no estoy seguro si avr-gcc lo admite). Si la llamada recursiva es lo último en una función, significa que a veces es posible evitar alterar el marco de la pila. El compilador puede simplemente reutilizar el marco existente, ya que la llamada 'padre' (por así decirlo) ha terminado de usarlo. Eso significará que, en teoría, puede seguir recurriendo tanto como desee, siempre que su función no llame a nada más.
fuente
Tenía exactamente la misma pregunta que cuando estaba leyendo Jumping into C ++ por Alex Allain , Ch 16: Recursion, p.230, así que realicé algunas pruebas.
TLDR;
Mi Arduino Nano (ATmega328 mcu) puede realizar 211 llamadas a funciones recursivas (para el código que se proporciona a continuación) antes de que se produzca un desbordamiento de pila y fallas.
En primer lugar, permítanme abordar esta afirmación:
[Actualización: ah, leí la palabra "rápido". En ese caso tienes cierta validez. Sin embargo, creo que vale la pena decir lo siguiente.]
No, no creo que sea una afirmación verdadera. Estoy bastante seguro de que todos los algoritmos tienen una solución recursiva y no recursiva, sin excepción. Es solo que a veces es significativamente más fácilusar un algoritmo recursivo. Dicho esto, la recursión está muy mal vista para su uso en microcontroladores y probablemente nunca se permitirá en un código crítico para la seguridad. Sin embargo, es posible hacerlo en microcontroladores. Para saber qué tan "profundo" puede entrar en cualquier función recursiva, ¡solo pruébelo! Ejecútelo en su aplicación de la vida real en un caso de prueba de la vida real y elimine su condición base para que se repita infinitamente. Imprima un contador y vea por usted mismo cuán "profundo" puede llegar para saber si su algoritmo recursivo está empujando los límites de su RAM demasiado o no para ser usados de manera práctica. Aquí hay un ejemplo a continuación para forzar el desbordamiento de la pila en un Arduino.
Ahora, algunas notas:
La cantidad de llamadas recursivas o "marcos de pila" que puede obtener está determinada por una serie de factores, que incluyen:
free_RAM = total_RAM - stack_used - heap_used
o podrías decirfree_RAM = stack_size_allocated - stack_size_used
)Mis resultados:
Segmentation fault (core dumped)
#pragma GCC optimize ("-O0")
al principio del archivo y rehacer:Here are the final print results: 209 210 211 ⸮ 9⸮ 3⸮
El código:
La aplicación para PC:
El programa "Sketch" de Arduino:
Referencias
#pragma GCC optimize
comando ya que sabía que lo tenía documentado allí.fuente
#pragma
que estás usando allí. En cambio, puede agregar__attribute__((optimize("O0")))
a la función única que desea no optimizar.Escribí este sencillo programa de prueba:
Lo compilé para el Uno, y mientras escribo, ¡ha recurrido más de 1 millón de veces! No lo sé, pero el compilador puede haber optimizado este programa.
fuente
call xxx
/ret
porjmp xxx
. Esto equivale a lo mismo, excepto que el método del compilador no consume la pila. Por lo tanto, podría repetir miles de millones de veces con su código (en igualdad de condiciones).#pragma GCC optimize ("-O0")
a la parte superior de su programa Arduino. Creo que tiene que hacer esto en la parte superior de cada archivo al que desea que se aplique, pero no lo he buscado en años, así que investigue por usted mismo para estar seguro.