Cómo optimizar su código C
1. Optimización de la estructura del programa
1. Estructura de escritura del programa
Aunque el formato de escritura no afecta la calidad del código generado, en la escritura real. del programa Aún debe seguir ciertas reglas escritas. Un procedimiento claramente escrito y comprensible será beneficioso para el mantenimiento futuro. Al escribir un programa, especialmente para declaraciones como While, for, do... while, if...elst, switch...case, o combinaciones anidadas de estas declaraciones, debe utilizar la forma de escritura "sangrada".
2. Identificadores
Además de seguir las reglas de nomenclatura para los identificadores, los identificadores de usuario utilizados en los programas generalmente no utilizan símbolos algebraicos (como a, b, x1, y1) como variables. Los nombres de palabras en inglés (o abreviaturas) con significados relevantes o pinyin chino se utilizan como identificadores para aumentar la legibilidad del programa, como: contar, número1, rojo, trabajo, etc.
3. Estructura del programa
El lenguaje C es un lenguaje de programación de alto nivel que proporciona una estructura de control de procesos estandarizada muy completa. Por lo tanto, cuando utilizamos el lenguaje C para diseñar programas de sistemas de aplicaciones de microcontroladores, primero debemos prestar atención a utilizar métodos de programación estructurados tanto como sea posible, de modo que toda la estructura del programa del sistema de aplicaciones pueda ser clara y fácil de depurar y mantener. Para un programa de aplicación más grande, el programa completo generalmente se divide en varios módulos según la función, y diferentes módulos completan diferentes funciones. Cada módulo puede ser escrito por separado, o incluso por diferentes programadores. Generalmente, las funciones completadas por un solo módulo son relativamente simples y el diseño y la depuración son relativamente fáciles. En lenguaje C, una función puede considerarse como un módulo. La llamada modularización del programa no solo significa dividir todo el programa en varios módulos funcionales, sino que, lo que es más importante, también se debe prestar atención a mantener la relativa independencia de las variables entre módulos, es decir, mantener la independencia de los módulos y utilizar variables globales como lo menos posible. Algunos módulos funcionales de uso común también se pueden encapsular en una biblioteca de aplicaciones para que se puedan llamar directamente cuando sea necesario. Sin embargo, cuando se utiliza la modularización, si el módulo se divide en demasiado fino y demasiado pequeño, la eficiencia de ejecución del programa será baja (proteger y restaurar registros lleva algún tiempo al entrar y salir de una función).
4. Definir constantes
En el proceso de diseño programático, para algunas constantes de uso frecuente, si las escribe directamente en el programa, una vez que el valor de la constante cambie, lo hará. Todas las constantes del programa deben encontrarse una por una y modificarse una por una, lo que inevitablemente reducirá la capacidad de mantenimiento del programa. Por lo tanto, debería intentar utilizar comandos de preprocesamiento para definir constantes y evitar errores de entrada.
5. Reducir las sentencias de juicio
Siempre que se pueda utilizar la compilación condicional (ifdef), utilice la compilación condicional en lugar de las sentencias if, lo que ayudará a reducir la longitud del código generado por la compilación y se puede utilizar sin utilizar declaraciones menos críticas.
6. Expresiones
Cuando la prioridad de ejecución de varias operaciones en una expresión no está clara o se confunde fácilmente, se deben utilizar paréntesis para especificar claramente su prioridad. Por lo general, una expresión no se puede escribir de manera demasiado compleja. Si la expresión es demasiado compleja, no le resultará fácil comprenderla con el tiempo, lo que no favorece el mantenimiento futuro.
7. Funciones
Para las funciones del programa, antes de usarlas, se debe describir el tipo de función. La descripción del tipo de función debe garantizar que sea coherente con la. El tipo de función definido originalmente. Las funciones sin parámetros y sin tipo de retorno deben especificarse como "nula". Si necesita acortar la longitud del código, puede definir algunos segmentos de programa comunes en el programa como funciones. Este es el caso de la optimización de alto nivel en Keil. Si necesita acortar el tiempo de ejecución del programa, reemplace algunas funciones con definiciones de macro después de depurar el programa. Tenga en cuenta que las macros deben definirse después de que se complete la depuración del programa, porque la mayoría de los sistemas de compilación informarán errores después de la expansión de las macros, lo que aumentará la dificultad de la depuración.
8. Utilice las variables globales lo menos posible y utilice más las variables locales.
Debido a que las variables globales se colocan en la memoria de datos, definir una variable global significa que la MCU tendrá un espacio de memoria de datos menos disponible. Si se definen demasiadas variables globales, el compilador no tendrá suficiente memoria. ser asignado.
La mayoría de las variables locales están ubicadas en los registros dentro de la MCU. En la mayoría de las MCU, la velocidad de operación del uso de registros es más rápida que la de la memoria de datos y las instrucciones son cada vez más flexibles, lo que favorece la generación de código de mayor calidad. y las variables locales ocupan menos espacio. Los registros y la memoria de datos se pueden reutilizar en diferentes módulos.
9. Establezca las opciones de compilación adecuadas
Muchos compiladores tienen varias opciones de optimización diferentes. Debe comprender el significado de cada opción de optimización antes de usarla y luego elegir la más adecuada. forma de optimizar. Por lo general, una vez que se selecciona la optimización de nivel más alto, el compilador buscará la optimización del código de manera casi patológica, lo que puede afectar la corrección del programa y provocar errores de ejecución. Por lo tanto, debe estar familiarizado con el compilador que está utilizando y saber qué parámetros se verán afectados durante la optimización y cuáles no.
En ICCAVR, hay dos opciones de optimización: "Predeterminado" y "Habilitar compresión de código".
En CodeVisionAVR, hay dos modos de memoria: "Tiny" y "small".
En IAR, hay 7 opciones diferentes de modo de memoria.
Hay más opciones de optimización en GCCAVR y es más fácil seleccionar opciones inapropiadas accidentalmente.
2. Optimización del código
1. Elija algoritmos y estructuras de datos adecuados
Debe estar familiarizado con el lenguaje del algoritmo y conocer las ventajas y desventajas de varios algoritmos. e información específica Consulte los materiales de referencia correspondientes, que se presentan en muchos libros de computadora. Reemplazar el método de búsqueda secuencial más lento con la búsqueda binaria más rápida o el método de búsqueda desordenada, y reemplazar el método de clasificación por inserción o de burbuja con clasificación rápida, clasificación por combinación o clasificación raíz puede mejorar en gran medida la eficiencia de la ejecución del programa. También es importante elegir una estructura de datos adecuada. Por ejemplo, si utiliza una gran cantidad de instrucciones de inserción y eliminación en un conjunto de números almacenados aleatoriamente, es mucho más rápido utilizar una lista vinculada.
Las matrices y las declaraciones de punteros tienen una relación muy estrecha. En términos generales, los punteros son más flexibles y concisos, mientras que las matrices son más intuitivas y fáciles de entender. Para la mayoría de los compiladores, el uso de punteros genera código más corto y más eficiente que el uso de matrices. Pero en Keil ocurre lo contrario: el uso de matrices genera un código más corto que el uso de punteros. .
3. Utilice el tipo de datos más pequeño posible
Si puede utilizar caracteres (char) para definir variables, no utilice variables enteras (int) para definirlas; No utilice el tipo int largo (long int) para variables definidas y, si puede, no utilice variables de tipo punto flotante (float). Por supuesto, después de definir una variable, no exceda el alcance de la variable. Si asigna un valor más allá del alcance de la variable, el compilador de C no informará un error, pero el resultado de ejecución del programa será incorrecto y tales errores. son difíciles de encontrar.
En ICCAVR, puede configurar el uso de los parámetros printf en Opciones, intente usar parámetros básicos (especificadores de formato %c, %d, %x, %X, %u y %s), menos uso parámetros enteros largos (especificadores de formato %ld, %lu, %lx y %lX). En cuanto a los parámetros de punto flotante (%f), intente no utilizarlos. Lo mismo ocurre con otros compiladores de C. Cuando otras condiciones permanecen sin cambios, el uso del parámetro %f aumentará en gran medida la cantidad de código generado y reducirá la velocidad de ejecución.
4. Utilice instrucciones de autoincremento y autodecremento.
Por lo general, utiliza instrucciones de autoincremento y autodecremento y expresiones de asignación compuestas (como a-=1 y a+=1). , etc.) pueden Para generar código de programa de alta calidad, los compiladores generalmente pueden generar instrucciones como inc y dec. Cuando se utilizan instrucciones como a=a+1 o a=a-1, muchos compiladores de C generarán dos o. Instrucciones de tres bytes. Los códigos generados por los métodos de escritura anteriores son los mismos en ICCAVR, GCCAVR, IAR y otros compiladores de C adecuados para chips individuales AVR, y también pueden generar códigos inc y dec de alta calidad.
5. Reducir la intensidad de los cálculos
Puedes reemplazar expresiones complejas originales con expresiones que requieran menos cálculo pero que tengan la misma función. Como sigue:
(1), operación restante.
a=a%8;
Se puede cambiar a:
a=a&7;
Nota: la operación de bit solo requiere uno instrucción Se puede completar en un ciclo, pero la operación "%" de la mayoría de los compiladores de C se completa llamando a subrutinas, lo que resulta en un código largo y una velocidad de ejecución lenta. Por lo general, si el único requisito es encontrar el resto de 2n cuadrados, se pueden utilizar operaciones de bits.
(2), operación cuadrada
a=pow(a,2.0);
Se puede cambiar a:
a= a *a;
Nota: En microcontroladores con multiplicadores de hardware incorporados (como la serie 51), la operación de multiplicación es mucho más rápida que la operación del cuadrado, porque el cuadrado de los números de coma flotante se implementa mediante llamar a una subrutina Sí, en microcontroladores AVR con multiplicadores de hardware incorporados, como ATMega163, la operación de multiplicación se puede completar en solo 2 ciclos de reloj. Incluso en los microcontroladores AVR sin multiplicadores de hardware incorporados, el código de subrutina para operaciones de multiplicación es más corto y rápido que las operaciones de subrutina para operaciones cuadradas.
Si es para encontrar la tercera potencia, como por ejemplo:
a=pow(a,3.0);
Cambiar a:
a =a*a*a;
La mejora de la eficiencia es más obvia.
(3) Utilice el desplazamiento para implementar operaciones de multiplicación y división
a=a*4;
b=b/4;
Se puede cambiar a:
a=a<<2;
b=b>>2;
Nota: Generalmente, si necesita multiplicar o dividir 2n se puede reemplazar cambiando. En ICCAVR, si multiplica por 2n, puede generar un código de desplazamiento a la izquierda, y cuando multiplica por otros números enteros o divide por cualquier número, llama a la subrutina de multiplicación y división. El código generado por el método de desplazamiento es más eficiente que el código generado llamando a las subrutinas de multiplicación y división. De hecho, siempre que se multiplique o divida por un número entero, el resultado se puede obtener cambiando, como por ejemplo:
a=a*9
Se puede cambiar a:
p>
a=(a<<3)+a
6. Bucle
(1), lenguaje de bucle
Para algunos bucles que no necesitan ser bucles Las tareas donde las variables participan en las operaciones se pueden colocar fuera del bucle. Las tareas aquí incluyen expresiones, llamadas a funciones, operaciones de puntero, acceso a matrices, etc. Todas las operaciones que no son necesarias. Los archivos que se realizarán varias veces deben reunirse y colocarse en un solo inicio realizado durante el procedimiento de inicialización.
(2) Función de retardo:
Las funciones de retardo de uso común tienen la forma de autoadición:
retraso nulo (void)
{
unsigned int i;
for (i=0;i<1000;i++)
}
Cámbialo a una función de retardo autodecreciente:
void delay (void)
{
unsigned int i;
for (i= 1000;i>0;i--)
}
Los efectos de retardo de las dos funciones son similares, pero casi todos los compiladores de C generan código para la última función. Son 1~ 3 bytes menos que el código anterior, porque casi todas las MCU tienen instrucciones para la transferencia 0, y el último método puede generar dichas instrucciones.
Lo mismo ocurre cuando se utilizan bucles while. El uso de instrucciones de autoincremento para controlar el bucle generará de 1 a 3 letras menos de código que el uso de instrucciones de autoincremento para controlar el bucle.
Sin embargo, cuando hay instrucciones para leer y escribir la matriz a través de la variable de bucle "i" en el bucle, cuando se utiliza el bucle de pre-decremento, la matriz puede salirse de los límites, así que preste atención. .
(3)bucle while y bucle do... while
Cuando se utiliza el bucle while, existen las siguientes dos formas de bucle:
unsigned int i;
i=0;
mientras (i<1000)
{
i++;
// Programa de usuario p>
}
O:
unsigned int i;
i=1000;
do
i--;
//Programa de usuario
while (i>0);
En estos dos bucles, use do. ..bucle while La longitud del código generado después de la compilación es más corta que la del bucle while.
7. Tabla de consulta
En el programa generalmente no se realizan operaciones muy complejas, como multiplicación, división y raíz cuadrada de números de coma flotante, así como operaciones de interpolación. Para algunos modelos matemáticos complejos, para estas operaciones que consumen tiempo y recursos, se deben usar tablas de búsqueda tanto como sea posible y las tablas de datos deben colocarse en el área de almacenamiento del programa. Si es difícil generar directamente las tablas requeridas, intente calcularlas primero al inicio, luego genere las tablas requeridas en la memoria de datos y luego busque las tablas directamente cuando el programa se esté ejecutando, lo que reduce la necesidad de cálculos repetidos durante carga de trabajo del programa.