Pregunta:
¿Cuáles son las formas tradicionales de optimizar el uso de la memoria del programa?
Mayoogh Girish
2020-07-01 13:34:12 UTC
view on stackexchange narkive permalink

Al realizar grandes proyectos utilizando placas Arduino (Uno, Atmega328P MCU). Solía ​​recibir advertencias como esta

  Sketch usa 13764 bytes (44%) del espacio de almacenamiento del programa. El máximo es 30720 bytes. Las variables globales usan 1681 bytes (82%) de memoria dinámica, dejando 367 bytes para las variables locales. El máximo es 2048 bytes. Memoria disponible baja, pueden ocurrir problemas de estabilidad.  
  • ¿Cuáles son los métodos generalmente practicados para el uso de memoria del programa de optimización?
  • ¿Hay cualquier diferencia en el uso de memoria si la variable se declara global o localmente.
  • ¿Importará cuáles son las declaraciones de control / selección (como if , switch )
  • Uso de monitor serial. Serial.print()
  • ......

Baja memoria disponible, pueden ocurrir problemas de estabilidad.

¿Qué tan graves son estas advertencias?

Antes de marcarlo como duplicado, me he referido a lo siguiente. Pero no fue satisfactoria
La forma más eficiente de programar con memoria
¿Cuáles son los límites seguros de uso de memoria?

no de una manera tradicional, pero Nano Every tiene 3 veces la SRAM del Nano clásico y el compilador actualizado no copia cadenas constantes en SRAM.
Cuatro respuestas:
Michel Keijzers
2020-07-01 13:48:19 UTC
view on stackexchange narkive permalink

¿Cuáles son los métodos generalmente practicados para el uso de la memoria del programa de optimización?

Primero, tenga en cuenta que está buscando formas de reducir la memoria SRAM. Esto contiene memoria global (variable) y espacio de pila (memoria dinámica + memoria de pila).

  • Evite los huecos de memoria, al no usar memoria dinámica (con free / malloc / new).
  • Evite el uso de la clase String .
  • Evite la memoria global en SRAM utilizando PROGMEM , F (..) si es posible.
  • Utilice el tamaño de variable más pequeño (por ejemplo, uint8_t en lugar de int).
  • Almacene matrices de valores booleanos en bits (8 valores booleanos por byte).
  • Use indicadores de bits si corresponde.
  • Use algún tipo de memoria comprimida internamente (afecta el rendimiento), por ejemplo si tiene que almacenar muchos valores de 6 bits, no los almacene en bytes separados, sino que use 6 bytes para valores de 8 por 6 bits).
  • Evite pasar matrices por valor.
  • Evite un pila de llamadas profunda con muchas (grandes) variables. Tenga en cuenta que esto afecta su diseño, así que utilícelo como último recurso.
  • Si necesita una tabla calculable, calcule cada valor en lugar de almacenarlo como una tabla.
  • No use más matrices de lo necesario, y piense en tamaños máximos razonables de matriz de lo contrario (consulte el comentario de hcheung a continuación).
  • (esta no es una lista completa).

existe alguna diferencia en el uso de la memoria si la variable se declara global o localmente.

Sí, las variables locales se agregan a la pila, pero se eliminan después de que finaliza la función, las variables globales permanecen (pero solo creado una vez) Tenga en cuenta que las variables de la pila (y también la memoria dinámica) NO se tienen en cuenta en la memoria calculada en el mensaje de advertencia durante la compilación.

¿Importará cuál sea la instrucción de control / Las declaraciones de selección son (como if, switch)

No, esto solo afectará a la memoria del programa.

Uso del monitor serial. Serial.print ()

Probablemente sí, el monitor serial probablemente reserva (¿bastante?) algo de memoria como búfer.

Hay poca memoria disponible, pueden ocurrir problemas de estabilidad. ¿Qué tan graves son estas advertencias?

Qué tan malo sea, depende de la cantidad de memoria que se use, que no se calcula, que es memoria dinámica y memoria de pila.

Puede calcularlo manualmente (lo que puede ser bastante engorroso para un programa grande), también puede usar la biblioteca de GitHub para esto:

Arduino MemoryFree

Si sabe cuánta memoria de pila usa peor caso, luego agréguelo a la memoria de variables globales calculadas. Si es menor que su memoria SRAM máxima disponible, está seguro.

Vota a favor del resumen completo. Me gustaría agregar uno más "asigne el tamaño de su matriz de acuerdo con la necesidad", a menudo veo que muchos usuarios de ArduinoJson crearon una asignación tan grande como 1024 bytes con `StaticJsonDocument <1024> doc;` para un objeto de datos de destino que consta solo de un par de pares clave / valor que no ocupan más de 50 bytes.
@hcheung ... bueno ... para mí es obvio que no usar demasiados, pero con un sistema de RAM limitado, debe pensar en los límites de la matriz con cuidado.
¿Qué pasa con macros como #define?
@MayooghGirish # define's son solo sustituciones textuales, el resultado de la sustitución se puede aplicar a los ejemplos mencionados en mi lista.
Nitpick: Yo diría "evitar usar` String` "en lugar de" evitar usar `String`". "Evitar" significa que no debe hacerlo. "Prevenir" significa que debe hacerlo para que otra persona no pueda hacerlo o para que no suceda por sí solo. Del mismo modo, "evite almacenar * constantes * variables globales en SRAM" (la parte constante es importante, ¡no puede simplemente mover * cualquier cosa * a PROGMEM!)
@user253751 Tienes razón, gracias, el inglés no es mi lengua materna. Lo cambiaré en mi respuesta.
He realizado algunos cambios más, en caso de que desee comprobarlos.
@user253751 Todo aceptado, utilicé 'sketch' porque en Arduino es el término 'predeterminado', aunque prefiero programa ya que se usa en todos los demás lugares.
Los programas de Arduino se llaman bocetos, pero creo que la memoria de programa todavía se llama memoria de programa, no memoria de boceto. Por ejemplo, `PROGMEM`
@user253751 Eso es correcto. Probablemente Arduino usó 'boceto' ya que parece menos 'difícil' que un programa.
Edgar Bonet
2020-07-01 20:21:50 UTC
view on stackexchange narkive permalink

Solo quiero agregar una viñeta a la excelente respuesta de Michel Keijzers:

  • Piense en cada elemento que está almacenando en la memoria y pregúntese: ¿realmente necesito conservar esto? en RAM?

Puede sonar tonto decir lo que muchos considerarían obvio, pero hemos visto aquí muchos casos de principiantes que no toman esto en consideración. Como ejemplo simple, considere esta función que promedia 500 lecturas analógicas:

  int averageAnalogReading () {// Primero tome y almacene las lecturas. int lecturas [500]; para (int i = 0; i < 500; i ++) lecturas [i] = analogRead (inputPin); // Luego calcula el promedio. suma larga = 0; para (int i = 0; i < 500; i ++) suma + = lecturas [i]; return sum / 500;}  

Almacenar todas esas lecturas es completamente inútil, ya que puede actualizar la suma sobre la marcha:

  int averageAnalogReading () {suma larga = 0; para (int i = 0; i < 500; i ++) suma + = analogRead (inputPin); return sum / 500;}  

Por la misma razón, si necesita algún tipo de promedio móvil para suavizar los datos, debería considerar usar un promedio móvil ponderado exponencialmente, que puede actualizarse gradualmente sin almacenar etiquetas.

Voto a favor, muy buen punto. La mejor manera de mejorar es verificar los algoritmos en sí. Nota breve: si está interesado en los promedios, también mire los 'promedios móviles', además de no costar SRAM adicional por cada medición realizada.
@MichelKeijzers Si desea un promedio móvil de filtro de caja adecuado, entonces necesita almacenar las últimas N mediciones. Pero si puede cambiarlo a un filtro exponencial, entonces no lo hará.
@user253751 Tiene razón en eso. Gracias por la aclaración.
Artelius
2020-07-02 08:59:09 UTC
view on stackexchange narkive permalink

¿Cuáles son los métodos generalmente practicados para el uso de memoria del programa de optimización?

(nb. según el comentario de Edgar, enfatizo que se trata de usar PROGMEM de manera más eficiente).

  • Si puede reemplazar el código con una tabla cuyo tamaño sea ≤ las líneas de código, hágalo.

    • En lugar de usar una secuencia de ifs, encuentre una manera de colapsar el procedimiento en una tabla
    • Use tablas de punteros de función si tiene sentido
    • A veces puede encontrar un mini-lenguaje que es mucho más denso que AVR instrucciones, por ejemplo, codificar la lógica del robot en 16 comandos, y luego puede empaquetar dos comandos por byte . Esto podría reducir el uso de la memoria 50 veces.
  • Use funciones en lugar de código repetido; esto puede parecer obvio, pero a menudo hay formas sutiles de reescribir el código (pero tenga en cuenta que las llamadas a funciones tienen sobrecarga)
  • Use tablas hash en lugar de tablas con grandes espacios
  • Use punto fijo en lugar de punto flotante (por ejemplo, puede tomar un byte e interpretar su valor como un rango de 0.00 a 2.55, en lugar de usar un flotante de 4 bytes)

¿Hay alguna diferencia en el uso de la memoria si la variable se declara global o localmente?

Hablemos de la pila.

  void A () {byte a [600]; ...} void B () {byte b [400]; ...} bucle vacío () {byte xxx [1000]; ...}  

Este programa primero utilizará al menos 1000 bytes de RAM todo el tiempo. No hay una diferencia real en comparación con declarar xxx a nivel mundial. Pero entonces lo que es crítico es qué función llama a cuál.

Si loop () llama a A (), y luego loop () llama a B (), el programa no usará más de 1600 en ningún momento. Sin embargo, si A () llama B (), o viceversa, el programa usará 2000. Para ilustrar:

  loop () [1000] └── ── A () [1600] │ [1000] └──── B () [1400] └──── A () [1600] └──── B () [1400]  

versus

  loop () [1000] └──── A () [1600]
└──── B () [2000] │ [1000] └──── A () [1600] └──── B () [2000]  

¿Importará cuáles son las declaraciones de control / selección (como if, switch)

No hay mucha diferencia para un pequeño número de casos. De lo contrario, depende de su código. La mejor manera es probar ambos y ver cuál es mejor. Pero:

switch es usualmente usan tablas de salto que son bastante compactas si cubre casi todos los casos en un rango (0,1,2,3,4, .., 100 ). Los if usualmente usan una secuencia de instrucciones, que ocupan más bytes y ciclos que una entrada de tabla de salto, pero tiene más sentido si no tiene un tramo consecutivo de casos.

Uso del monitor serial. Serial.print ()

No creo que eso marque la diferencia. Los búferes de serie son pequeños (digamos 64 bytes o 128 para una placa más grande) y creo que se asignan tanto si usa Serial como si no.

Por supuesto, "cadenas literales como esta" y búferes char [] consumen memoria. Puede comentarlos (o usar #ifdef s) cuando no los necesite.

1. Tenga en cuenta que sus primeras 5 viñetas tratan de guardar flash en lugar de guardar RAM. El OP no se quedó corto en flash. Las tablas realmente _costarán_ RAM, a menos que las ponga en PROGMEM. 2. Re "_No hay una diferencia real en comparación con declarar xxx globalmente_": es decir, a menos que `setup ()` esté hambriento de memoria. 3. Los búferes seriales no se asignan si no usa `Serial`.
Gracias @EdgarBonet 1. Sí, estaba pensando en PROGMEM, lo siento. 2. ¿Podría explicar esto? ¿No se libera la memoria asignada en la pila de `setup ()` después de ejecutarse? 3. Gracias, lo he editado.
Vuelva a explicar el punto 2: `setup ()` y `loop ()` no suelen ejecutarse al mismo tiempo, por lo que sus usos de pila no cuadran. Si hace que `xxx` sea global, se asignará incluso mientras se esté ejecutando` setup () `. Esto no debería ser motivo de preocupación a menos que `setup ()` consuma mucha memoria.
David G.
2020-07-03 18:34:02 UTC
view on stackexchange narkive permalink

Ya que solicitó métodos tradicionales , voy a plantear un método tradicional. En este caso, más de 50 años.

Generar y analizar un listado.

Metodología:

  1. Compilar todo el código con depuración activada (agregar -g).

  2. Vincular el código con depuración activada, produciendo un ejecutable ELF. NO convierta a una imagen que se pueda cargar en el arduino.

  3. use objdump para hacer una lista. Mi uso de esta lectura:

      (avr-objdump --headers --source --disassemble --syms program.elf; \ avr-objdump --full-contents --section =. final_progmem program.elf) > program.lst  

    Su uso puede variar según lo que prefiera ver.

El punto de esto es que esto le permite ver exactamente qué está usando cada byte de su memoria.

Es posible que desee jugar con diferentes optimizaciones, hasta que comprenda lo que está viendo. -O0 produce el desmontaje más comprensible, mientras que -Os hace el más pequeño.

Una sugerencia basada en haber hecho esto: las bibliotecas Arduino están diseñadas para generalidad, no velocidad y eficiencia de la memoria.



Esta pregunta y respuesta fue traducida automáticamente del idioma inglés.El contenido original está disponible en stackexchange, a quien agradecemos la licencia cc by-sa 4.0 bajo la que se distribuye.
Loading...