Red de conocimiento informático - Material del sitio web - Cómo optimizar la escritura de código de programa C

Cómo optimizar la escritura de código de programa C

Primer movimiento: intercambiar espacio por tiempo

La mayor contradicción en los programas de computadora es la contradicción entre espacio y tiempo. Luego, desde esta perspectiva, invierta el pensamiento para considerar la eficiencia del programa. Tenemos la primera solución al problema del intercambio de espacio por tiempo. Por ejemplo, asignación de cadenas:

Método A: el método habitual #define?LEN?32

char?string1?[LEN];

memset? string1, 0, LEN);

strcpy?(string1, "¡¡Este?es?un?ejemplo!!");

Método B:

const ?char?string2[LEN]?="¡Este?es?un?ejemplo!";

char?*?cp;

cp?=?string2?;

char?*?cp; p>

Puede usar punteros directamente para operar cuando lo usa.

Como se puede ver en el ejemplo anterior, la eficiencia de A y B es incomparable. En el mismo espacio de almacenamiento, B puede operar directamente usando punteros, mientras que A necesita llamar a funciones de dos caracteres para completar. La desventaja de B es que no es tan flexible como A. Cuando es necesario cambiar el contenido de una cadena con frecuencia, A tiene mayor flexibilidad; si se utiliza el método B, es necesario almacenar previamente muchas cadenas, aunque ocupa mucha memoria, pero logra una alta eficiencia de ejecución del programa.

Si el sistema tiene altos requisitos de tiempo real y todavía tiene algo de memoria, te recomiendo que utilices este truco. Consejo nº 2: utilice macros en lugar de funciones.

Esta también es una variación del primer movimiento. La diferencia entre funciones y macros es que las macros ocupan mucho espacio, mientras que las funciones ocupan tiempo. Lo que todo el mundo necesita saber es que las llamadas a funciones utilizan la pila del sistema para guardar datos. Si el compilador tiene una opción de verificación de pila, algunas declaraciones de ensamblaje generalmente están incrustadas en el encabezado de la función para verificar la pila actual; La CPU también debe Cuando se llama a la función, la escena actual se guarda y restaura, y la pila se empuja y extrae. Por lo tanto, la llamada a la función requiere algo de tiempo de CPU. Las macros no tienen este problema. Las macros solo están integradas en el programa actual como código preescrito y no generan llamadas a funciones, por lo que solo ocupan espacio. Este fenómeno es especialmente prominente cuando se llama con frecuencia a la misma macro.

Por ejemplo:

Método C: #define?bwMCDR2_ADDRESS?4

#define?bsMCDR2_ADDRESS?17

int?BIT_MASK( int?__bf)

{

retorno?((1U?lt;lt;?(bw?##?__bf))?-?1)lt;lt;?( bs?##?__bf);

}

void?SET_BITS(int?__dst,

int?__bf, ?int?__val)

{

__dst?=?((__dst)?amp;?~(BIT_MASK(__bf)))?|

(((__val)?lt;lt ;?(bs?##?__bf))

amp;?(BIT_MASK(__bf))))

}

SET_BITS(MCDR2,?MCDR2_ADDRESS ,ReGISterNumber);

Método D: #define?bwMCDR2_ADDRESS?4

#define?bsMCDR2_ADDRESS?17

#define?bmMCDR2_ADDRESS?BIT_MASK(MCDR2_ADDRESS)

#define?BIT_MASK(__bf)

(((1U?lt;lt;?(bw?##?__bf))?-?1)

lt;lt;?(bs?##?__bf))

#define?SET_BITS(__dst,?__bf,?__val)

((__dst)?=?( (__dst)?amp;?~(BIT_MASK(__bf)))

|

(((__val)?lt;lt;?(bs?##?__bf))

amp;?(BIT_MASK(__bf))))

SET_BITS(MCDR2,?MCDR2_ADDRESS,

Número de registro);

D El método es la mejor función de operación configurada que he visto. Es parte del código fuente de la compañía Arm. Implementa muchas funciones en solo tres líneas y cubre casi todas las funciones de operación de bits. El método C es una variación del mismo, cuyo sabor debe ser comprendido cuidadosamente por todos.

El tercer paso: métodos matemáticos para resolver problemas

Ahora deducimos el segundo paso de la escritura eficiente en lenguaje C: utilizar métodos matemáticos para resolver problemas. Las matemáticas son la madre de las computadoras. Sin la base y el fundamento de las matemáticas, no habría desarrollo de las computadoras. Por lo tanto, al escribir programas, el uso de algunos métodos matemáticos mejorará la eficiencia de ejecución del programa en órdenes de magnitud. Por ejemplo, encuentre la suma del 1 al 100.

Método E: int?I?,?j;

for?(I?=?1?;Ilt;=100;?I?)

{

j? =?I;

}

Método Fint?I;

I?=?(100?* ?(1 100))?/?2

Este ejemplo es el caso de uso matemático que más me impresionó. Fue probado por mi profesor de informática. En ese momento solo estaba en tercer grado de la escuela primaria. Desafortunadamente, no sabía cómo usar la fórmula N×(N 1)/2 para resolver este problema. El método E realizó 100 ciclos para resolver el problema, lo que significa que se utilizaron al menos 100 tareas, 100 juicios y 200 sumas (I y j); mientras que el método F solo utilizó 1 suma, 1 multiplicación y 1 división. El efecto es evidente.

Por lo tanto, cuando estoy programando ahora, uso más mi cerebro para encontrar reglas y maximizar el poder de las matemáticas para mejorar la eficiencia de la operación del programa. El cuarto truco: utilizar operaciones de bits

Utilizar operaciones de bits. Reducir las operaciones de división y módulo. En un programa de computadora, el bit de datos es la unidad de datos más pequeña que se puede manipular. En teoría, las "operaciones de bits" se pueden utilizar para completar todos los cálculos y operaciones. Las operaciones de bits generales se utilizan para controlar el hardware o realizar la transformación de datos. Sin embargo, las operaciones de bits flexibles pueden mejorar efectivamente la eficiencia de la operación del programa. Por ejemplo:

Método Gint?I,J;

I?=?257?/8;

J?=?456??32;

Método¿Sugerencia?I,J;

I?=?257?gt;gt;3;

J?=?456?-?(456? gt ;gt;?4?lt;lt;?4);

Literalmente, parece que H es mucho más problemático que G. Sin embargo, si observa detenidamente el código ensamblador generado, comprenderá que el método G llama a la función de módulo básica y la función de división incluyen llamadas a funciones, así como una gran cantidad de código ensamblador y registros involucrados en la operación, mientras que el método H es solo unas pocas oraciones de ensamblaje relacionado, y el código es más simple y eficiente; . Por supuesto, debido a los diferentes compiladores, la diferencia en eficiencia puede no ser grande. Sin embargo, a juzgar por el MS C y el brazo C que he encontrado hasta ahora, la diferencia en eficiencia sigue siendo bastante grande.

Para operaciones matemáticas con 2 factores elevados a la potencia "*", "/" o "", convertirlos en operaciones de desplazamiento "lt;lt;gt;gt;" generalmente puede mejorar la eficiencia del algoritmo. Porque el ciclo de instrucción de las operaciones de multiplicación y división suele ser mayor que el de las operaciones de turno.

Además de mejorar la eficiencia informática, las operaciones de bits en lenguaje C tienen otra aplicación más típica en la programación de sistemas integrados y se utilizan ampliamente en AND bit a bit ( amp;), o (|), no (~ ) operaciones, que tienen mucho que ver con las características de programación de los sistemas integrados. Por lo general, necesitamos configurar los bits de los registros de hardware. Por ejemplo, configuramos el sexto bit inferior del registro de control de la máscara de interrupción del procesador AM186ER 80186 en 0 (activar la interrupción 2. El método más común es:

).

#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

outword(INT_MASK, wTemp amp;~INT_I2_MASK);

Y establece este bit La forma de establecerlo en 1 es:

#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

outword(INT_MASK, wTemp | INT_I2_MASK);

La forma de determinar si el bit es 1 es:

#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

if(wTemp amp ; INT_I2_MASK)

{

… /* Este bit es 1 */

}

Al usar este truco , debes prestar atención a: Problemas causados ​​por diferentes CPU. Por ejemplo, si un programa escrito con este método en una PC y pasó la depuración en la PC puede causar peligros en el código cuando se trasplanta a una plataforma de computadora de 16 bits. Por lo tanto, este truco sólo se puede utilizar después de un cierto nivel de avance técnico. El quinto truco: incrustación de ensamblador

A los ojos de las personas que están familiarizadas con el lenguaje ensamblador, los programas escritos en lenguaje C son basura". Aunque esta afirmación es un poco extrema, tiene su verdad. El lenguaje ensamblador es Es el lenguaje informático más eficiente, pero es imposible escribir un sistema operativo basándose en él, ¿verdad? Por lo tanto, para obtener una alta eficiencia del programa, tenemos que adoptar un método flexible: ensamblaje integrado y programación híbrida.

El ensamblado en línea se utiliza principalmente en programas C integrados, es decir, las declaraciones de ensamblado en línea _asm{} se insertan directamente en el programa C.

Por ejemplo, asigne la matriz uno a la matriz dos, lo que requiere que cada byte coincida.

char string1[1024], string2[1024];

Método I

int I

for (I =0; Ilt; 1024; I )

*(cadena2 I) = *(cadena1 I)

Método J

#ifdef _PC_

int I;

for (I =0; Ilt; 1024; I)

*(cadena2 I) = *(cadena1 I);

#else

#ifdef _arm_

__asm

{

MOV R0, cadena1

MOV R1, cadena2

MOV R2, #0

bucle:

LDMIA R0!, [R3-R11]

STMIA R1!, [R3-R11 ]

AÑADIR R2, R2, #8

CMP R2, #400

bucle BNE

}

#endif

Otro ejemplo:

/* Suma los valores de los dos parámetros de entrada y almacena el resultado en otra variable global*/

int resultado;

void Add(long a, long *b)

{

_asm

{

MOV AX, a

MOV BX, b

AÑADIR AX, [BX]

Resultado MOV, AX

}

}

El método I es el método más común y utiliza 1024 bucles; el método J se diferencia según las diferentes plataformas. Bajo la plataforma del brazo, el ensamblaje integrado solo utiliza 128 veces. cosa. Algunos amigos aquí dirán, ¿por qué no utilizar la función de copia de memoria estándar? Esto se debe a que los datos de origen pueden contener bytes con datos 0. En este caso, la función de biblioteca estándar finalizará antes y no completará la operación que requerimos. Esta rutina se utiliza normalmente en el proceso de copia de datos LCD. Dependiendo de la CPU, el uso competente del ensamblaje integrado correspondiente puede mejorar en gran medida la eficiencia de la ejecución del programa.

Aunque es un movimiento seguro, pagará un alto precio si se usa con facilidad. Esto se debe a que el uso de ensamblaje integrado limita la portabilidad del programa, lo que hace que sea peligroso trasplantar el programa a diferentes plataformas. Al mismo tiempo, este truco también es contrario al pensamiento de la ingeniería de software moderna. ¡Solo se puede usar! en circunstancias forzadas. El sexto consejo es utilizar variables de registro

Cuando una variable se lee y escribe con frecuencia, es necesario acceder a la memoria repetidamente, lo que requiere mucho tiempo de acceso. Para ello, el lenguaje C proporciona un tipo de variable, a saber, variable de registro. Este tipo de variable se almacena en el registro de la CPU. Cuando se usa, no necesita acceder a la memoria, pero se puede leer y escribir directamente desde el registro, lo que mejora la eficiencia. El descriptor de una variable de registro es registro. Las variables de control de bucle con una gran cantidad de bucles y las variables utilizadas repetidamente en el cuerpo del bucle se pueden definir como variables de registro, y el conteo de bucles es el mejor candidato para aplicar variables de registro.

(1) Sólo las variables automáticas locales y los parámetros formales se pueden definir como variables de registro.

Debido a que las variables de registro son métodos de almacenamiento dinámico, cualquier cantidad que requiera métodos de almacenamiento estático no se puede definir como variables de registro, incluidas: variables globales entre módulos, variables globales dentro de módulos y variables estáticas locales;

(2 ) registro Es una palabra clave de "sugerencia", lo que significa que el programa recomienda que la variable se coloque en un registro, pero al final es posible que la variable no se convierta en una variable de registro porque no se cumplen las condiciones, pero se coloca en la memoria. , pero el compilador no informa un error (hay otra palabra clave de "sugerencia" en lenguaje C: en línea).

El siguiente es un ejemplo del uso de variables de registro:

/* Encuentre el valor de 1 2 n*/

Adición de palabras (BYTE n). )

{

registro i, s=0

for(i=1; ilt; =n; i )

{

s=s i;

}

return s;

}

Este programa se repite n veces , i y s se utilizan con frecuencia y, por lo tanto, pueden definirse como variables de registro. El séptimo consejo: Aprovecha las características del hardware

En primer lugar, debes entender la velocidad de acceso de la CPU a varias memorias, que es básicamente:

RAM interna de la CPU>RAM síncrona externa> RAM asincrónica externa gt; FLASH/ROM

Para el código del programa, que se ha grabado en FLASH o ROM, podemos dejar que la CPU lea directamente el código para su ejecución, pero normalmente esto no es bueno. De esta manera, será mejor que lo hagamos después de que se inicie el sistema, copie el código de destino en FLASH o ROM en la RAM y luego ejecútelo para mejorar la velocidad de obtención de instrucciones;

Para dispositivos como UART, existe una cierta capacidad de recibir BUFFER en su interior, y debemos intentar utilizar BUFFER tanto como sea posible. Una vez lleno, se genera una interrupción a la CPU. Por ejemplo, cuando una terminal de computadora transmite datos a la máquina de destino a través de RS-232, no es apropiado configurar el UART para que genere una interrupción en la CPU después de recibir solo un BYTE, lo que desperdiciará tiempo de procesamiento de interrupciones innecesariamente;

Si se puede utilizar DMA para un determinado dispositivo Para leer, utilice la lectura DMA. El método de lectura DMA es más eficiente cuando la información de almacenamiento contenida en el objetivo de lectura es grande. La unidad básica de transmisión de datos es un bloque. Los datos transmitidos se envían directamente desde el dispositivo de memoria (o viceversa). En comparación con el modo controlado por interrupciones, el modo DMA reduce la intervención de la CPU en los periféricos y mejora aún más el grado de operación paralela entre la CPU y los periféricos.

Lo anterior es mi resumen de cómo optimizar el código C.