xih
Al escribir y depurar programas C6000, para obtener el mejor rendimiento del código C6000, debemos seguir las tres etapas de la programación de software. Las tareas completadas en cada etapa son las siguientes [4]:
La primera etapa: al principio, puede ignorar el conocimiento relevante de C6000 y escribir programas en lenguaje C basados completamente en la tarea. Utilice la herramienta de generación de código C6000 en el entorno CCS para compilar y generar código que se ejecuta en el C6000 para demostrar que su función es correcta. Luego utilice herramientas de depuración CCS, como depuración y perfilador, para analizar y determinar las secciones ineficientes que pueden existir en el código y afectar el rendimiento. Para mejorar aún más el rendimiento del código, se requiere una segunda fase.
La segunda etapa: utilice funciones en línea, opciones de compilación CCS y otros métodos de optimización específicos para mejorar los programas en lenguaje C. Repita la primera etapa y verifique el rendimiento del código C6000 resultante. Si el código generado aún no puede lograr el rendimiento deseado, se ingresa a la tercera etapa.
La tercera etapa: extraiga los segmentos del programa que tienen un gran impacto en el rendimiento del programa en lenguaje C, reescribalos en ensamblaje lineal y luego use el optimizador de ensamblaje para optimizar y vincular hasta que se logren los requisitos de rendimiento deseados. .
Específicamente para los requisitos en tiempo real del códec estándar G.729A, la tercera etapa es el foco del trabajo, y la reescritura del ensamblaje lineal requiere una comprensión completa de las características del código del programa y DSP.
3. Análisis del código G.729A
El entorno de desarrollo integrado CCS proporciona a los desarrolladores de software herramientas eficientes de desarrollo y depuración. En particular, proporciona una herramienta de optimización para el generador de perfiles. Al recopilar el rendimiento estadístico de la ejecución del programa en un intervalo de código específico, analiza y determina el tiempo de procesador empleado por cada segmento y cada subfunción del programa, enfocando así la optimización. del programa en Enumere los segmentos de código que tienen el mayor impacto en el rendimiento del programa [5]. Los dos métodos de prueba diferentes son:
(1) Establezca dos puntos de interrupción al principio y al final del segmento del programa donde se debe medir la complejidad, abra la ventana del reloj y ejecute el programa. La ejecución se detiene en el primer punto de interrupción. En este momento, haga doble clic en la ventana del reloj para borrarla a 0. Luego continúe ejecutando el programa y deténgase en el segundo punto de interrupción. En este momento, el valor que se muestra en la ventana del reloj es la complejidad de. el código. Esto es muy útil para probar la complejidad de una función en un programa.
(2) Primero abra la ventana de estadísticas y establezca puntos estadísticos (Puntos de sonda) al principio y al final del segmento del programa que debe probarse. Después de ejecutar el programa, el valor estadístico detrás del programa. El segmento en la ventana de estadísticas es La complejidad del segmento de código. Este método es relativamente simple. Los puntos estadísticos recopilan automáticamente información estadística sin intervención manual. Esto es muy útil para medir la complejidad de múltiples segmentos de código del programa. >
4. Optimización del ensamblaje lineal <. /p>
El ensamblaje lineal es un lenguaje ensamblador proporcionado por TI. Su sistema de instrucción es exactamente el mismo que el del lenguaje ensamblador, pero no es necesario especificar registros y. unidades operativas al escribir, y no hay necesidad de considerar problemas de retraso, por lo tanto, es relativamente más fácil escribir un ensamblaje lineal [6]
Después de la primera y segunda etapa de optimización, la condición de ejecución del audio. El programa de codificación en DM642 se ha mejorado enormemente, pero después de la prueba, aún no se lograron efectos en tiempo real y la eficiencia del lenguaje de alto nivel casi se maximizó. La velocidad de prueba alcanzó 36,5 fotogramas/s. 10 veces más que antes de la optimización. En este momento, utilizamos lenguaje ensamblador lineal para reescribir el segmento ineficiente del código C, mejorar aún más la eficiencia de ejecución del programa y aprovechar al máximo los recursos de hardware de DM642, y finalmente implementar G. Codificación 729A en DM642 en tiempo real de acuerdo con los requisitos de diseño. Como se mencionó en el proceso de desarrollo de DSP anterior, el último método de desarrollo de DSP es reescribir C en ensamblaje porque es muy difícil escribir ensamblaje para procesadores paralelos, una programación híbrida. El método generalmente se adopta, es decir, la parte principal del programa está escrita en código C y la otra parte está escrita en código C. Las funciones que requieren mucho tiempo se pueden reescribir en ensamblaje lineal.
En el proceso de escritura de código optimizado para ensamblaje lineal, para mejorar la eficiencia de ejecución del código, debemos seguir los siguientes principios [7]:
(1) Escribir código paralelo: mediante el uso de instrucciones ensambladoras. El método de ejecución paralela reduce el número de ciclos de ejecución dentro del bucle y optimiza el código ensamblador lineal. La cuestión clave aquí es descubrir las dependencias de las instrucciones para que sólo se puedan ejecutar en paralelo instrucciones no relacionadas. Para identificar si las instrucciones están relacionadas, puede utilizar un diagrama de correlación.
(2) Procesamiento de instrucciones de salto e instrucciones de transferencia: una característica importante de los programas de ensamblaje son los saltos frecuentes. Cuando se cumplen diferentes condiciones, el programa debe realizar diferentes operaciones o saltar a la ubicación correspondiente. Los juicios y procesamientos lógicos estrictos como "mayor que", "mayor que o igual que", "menor que", "menor que o igual que" deben tratarse con precaución; de lo contrario, se producirán errores lógicos que serán difíciles de depurar. Este fenómeno es particularmente prominente cuando se produce un desbordamiento y debe manejarse en consecuencia.
(3) Minimizar el número de instrucciones en el cuerpo del bucle: muchas de las implementaciones del algoritmo de G.729A se completan dentro del bucle en algunos lugares, como el proceso de búsqueda del libro de códigos fijo, para poder hacerlo. determinar cuatro distintos de cero. La posición y la amplitud de los pulsos también se determinan mediante ciclos múltiples. Dentro de un bucle, especialmente dentro de un bucle profundamente anidado, reducir una instrucción puede reducir en gran medida la cantidad de operaciones del programa. Por ejemplo, para un bucle anidado cuádruple con 8 bucles por bucle, por cada instrucción reducida en el bucle más interno, el programa completo puede ejecutar 84 = 4096 instrucciones menos. Por lo tanto, al diseñar un programa, las declaraciones que se pueden ejecutar fuera del bucle deben ejecutarse fuera del bucle tanto como sea posible.
(4) Expandir el cuerpo del programa: bajo ciertas condiciones, expanda el programa tanto como sea posible para reducir la cantidad de llamadas y devoluciones de subrutinas, sacrificando espacio por tiempo.
El módulo LPC, la cuantificación LSP y la búsqueda del libro de códigos de excitación en el algoritmo G.729A toman la mayor parte del tiempo. Para mejorar aún más la eficiencia del código, algunas funciones, como los cálculos relacionados y el filtrado FIR, se reescribieron en lineal. lenguaje ensamblador y utilizar métodos como dibujar diagramas de correlación para llevar a cabo una optimización específica. Después de la optimización mediante el optimizador de ensamblaje, la eficiencia del código mejora significativamente en comparación con la compilación directa del lenguaje C.
5. Innovaciones en el trabajo de optimización
En la optimización de G.729A, este artículo presenta algunas ideas valiosas para los chips de la serie DSP TMS320DM642 basadas en resultados de investigaciones anteriores. . Estas innovaciones han mejorado la velocidad de optimización y la eficiencia de ejecución del código en diversos grados y desempeñaron un papel clave en la implementación en tiempo real de DSP para la codificación y decodificación de voz. A continuación, se ilustran algunos métodos clásicos con ejemplos.
5.1 Dibujar diagramas de análisis y dominar la estructura de la función
Para una función con muchas declaraciones y estructura compleja, para comprender completamente su estructura lógica y la correlación de declaraciones, generalmente usamos Método de diagrama de análisis de dibujo. La forma de los diagramas de análisis es relativamente flexible y se pueden seleccionar diferentes herramientas de dibujo según situaciones específicas. Al escribir un ensamblaje lineal, debe considerar cuestiones como el acceso a elementos en matrices, las operaciones de empaquetado de datos y los diagramas de análisis de correlación de datos que ayudan a manejar correctamente estos problemas.
En el proceso de optimización de la función Cor_h_X (), encontramos ciertas dificultades. La razón es que hay un cuerpo de bucle de doble capa. El número de veces en la capa interna está relacionado con la capa externa. El número de bucles en la capa exterior es 40 y las declaraciones dentro del bucle están relacionadas secuencialmente. Si dicha estructura utiliza expansión de bucle, se utilizará una gran cantidad de registros, el número excede 64. Es necesario abrir espacio de memoria adicional para almacenar variables temporales. La lectura y escritura de memoria consumirá mucho tiempo, por lo que la eficiencia de ejecución es. ineficiente Habrá suficiente mejora. En este sentido, utilizamos el diagrama de análisis para describir el uso de las matrices X[ ] y h[ ] de los códigos clave en la función, como se muestra en la Figura 1:
Figura 1 función cor_h_X() diagrama de análisis (parte)
La Figura 1 refleja intuitivamente la relación de multiplicación y suma entre la matriz h[ ] de 16 bits y la matriz temporal Y[ de 32 bits guardada en 16 bits ].
Al estudiar este diagrama de análisis, encontramos que algunos elementos en h[] y pueden guardar la cantidad de registros utilizados y eliminan la necesidad de abrir espacio de memoria y acceder a instrucciones para variables intermedias.
Para la función cor_h_X(), utilizando las ideas anteriores para escribir un ensamblaje lineal, solo es necesario definir 57 registros para completar las operaciones. Las instrucciones de acceso están optimizadas de 1760 a 30, que es solo 1/. 60. Al mismo tiempo, la velocidad de ejecución se reduce de 390072 relojes a 35871 relojes, lo que se reduce a 1/10 del original.
El diagrama de análisis dibujado puede incluir diagramas relacionados, tablas relacionadas, etc., lo que hace que la disposición de los recursos sea más razonable. Este método también se ha utilizado muchas veces al reescribir otras funciones.
5.2 Fusionar funciones o segmentos de código con funciones similares en una sola función
El ensamblaje lineal no solo mejora la eficiencia del código sino que también aumenta exponencialmente el tamaño del código. Tome el cor_h_X() anterior como ejemplo. , el tamaño de su código aumentó de 660 a 7776 después de escribirlo (estos datos fueron analizados por la herramienta de creación de perfiles CCS). En aplicaciones de ingeniería, para el área de programa de memoria limitada, reduciremos adecuadamente el espacio ocupado por el programa. Esto se puede lograr fusionando funciones con funcionalidades similares.
En el procesamiento de cuantificación de LSP, se proporcionan dos funciones de selección de LSP en el código fuente: Lsp_select_1 () y Lsp_select_2 (), y encontramos que tienen la misma función y estructura similar, por lo que en el Adaptación del ensamblaje lineal de los dos, solo necesitamos escribir una función (llamada Lsp_select) para realizar las funciones de estos dos módulos en el procesamiento de cuantificación LSP.
Además, para algunos códigos de copia e inicialización de matrices, también podemos usar este método para escribir una implementación de función, lo que puede mejorar la eficiencia de ejecución y reducir el espacio de memoria ocupado por el programa.
5.3 Fusionar varios bucles en uno
Al reescribir código C en un ensamblaje lineal, a menudo encontramos que siempre que se realicen algunos ajustes, las operaciones completadas por dos o más bucles son completamente Esto se puede hacer mediante un bucle. Tomando como ejemplo la función Autocorr() de cálculo de autocorrelación del habla en ventana de 240 puntos del submódulo LPC, el código C optimizado y reescrito (parte) es el siguiente:
for (i=0; ilt ; L_WINDOW; i) // Primer cuerpo del bucle
y[i] = (_smpy(x[i], hamwindow[i]) 0x00008000L) gt; sum = 1 ; //Evita la situación de 0
for (i=0; ilt; L_WINDOW; i) //Cuerpo del segundo bucle
sum = _sadd(sum, _smpy( y [i], y[i]));
Este código contiene dos bucles for. La compilación directa y la ejecución en CCS tienen un paralelismo deficiente, por lo que el código se reescribe mediante ensamblaje lineal. Descubrimos que el número de bucles de ambos cuerpos de bucle es 60 (L_WINDOW = 60), las matrices procesadas son diferentes y los dos bucles no están relacionados, por lo que el primer y segundo bucle se pueden fusionar en un solo bucle. La función del primero es visualizar la señal de voz; el segundo es implementar la multiplicación y acumulación (Mac).
Una vez fusionados, se escriben en ensamblaje lineal. El código es el siguiente:
mvk 60, i // Establece el número de bucles
loop1: lddw *ham, hamih: hamil //hamwindow[] puntero
lddw *x, xih: xil //x[] puntero
smpy2 hamil, xil, yi1: yi0 //Dos pares de 16 Los operandos de bits se heredan y ejecutan en paralelo
smpy2 hamih, xih, yi3: yi2
sadd yi0, con0x8000, yi0
sadd yi1, con0x8000, yi1
sadd yi2, con0x8000, yi2
sadd yi3, con0x8000, yi3
packh2 yi1, yi0, yl //tecnología de empaquetado de datos
packh2 yi3, yi2, yh
stdw yh: yl, *y //Doble acceso a palabras, mejora la eficiencia de ejecución
smpy2 yl, yl, yi1: yi0
sadd suma0, yi1, suma0
sadd suma0, yi0, suma0
smpy2 yh, yh, yi3: yi2
sadd suma0, yi3, suma0
sadd sum0, yi2, sum0
add i, -1, i
[i] b loop1 // Combina el primer y segundo bucle en un bucle grande para reducir el número de transferencias
El rendimiento de la canalización paralela del código ensamblador generado aumenta considerablemente y el número de ciclos de reloj consumidos se reduce de 1.310.000 a 15.000, que es menos de 1/8 del anterior la adaptación.
6. Conclusión
En cuanto al ciclo de reloj de ejecución del códec, antes y después de reescribir el ensamblaje lineal, la versión del archivo se conoce a través de la herramienta de análisis de perfiles de CCS: cada 10 fotogramas (100 MS) de 159700000 se redujeron a 68500000, sólo 42 del número original. Se probó la versión de hardware y el número de fotogramas codificados y decodificados se incrementó a más de 88 fotogramas/s. Dado que la relación de tiempo de codificación y decodificación es de 5:1, la codificación de este sistema ha alcanzado los 100 fotogramas/s, lo que cumple plenamente. los requisitos de la comunicación en tiempo real.