2009-11-24 17 views
5

¿Cuál es la forma más poco ortodoxa de mejorar el rendimiento del código C? ¡Esto no tiene límites! Todo va incluido el cambio de estructuras de bucle a gotos, hardcoding todo y cualquier cosa, utilizando sentencias de casos de maneras extrañas, etc. No se preocupe en absoluto por el mantenimiento, la legibilidad, etc.Mejorando el rendimiento del código C

p.s. Este es práctico ... y soy muy consciente de cómo mejorar el rendimiento del código de manera razonable (mejorar los algoritmos, perfil antes de optimizar, etc.)

+1

No hay pruebas de que ir en contra del lenguaje y para qué están "optimizados" los compiladores le dará un impulso en el rendimiento. – AraK

+3

¿Desde cuándo mejorar los algoritmos, perfilar antes de optimizar, etc. es razonable? Si eso fuera cierto, no tendríamos que trabajar tan duro para convencer a la gente de hacer estas cosas. – jason

+0

He votado para volver a abrir. Me hubiera gustado agregar una respuesta, a saber, este enlace: http://stackoverflow.com/questions/926266/performance-optimization-strategies-of-last-resort/927773#927773 –

Respuesta

18

En mi experiencia, la forma más poco ortodoxa de optimizar el código C es crear un perfil de la aplicación, identificar las estructuras de ejecución lenta y/o los éxitos de DB y luego diseñar soluciones razonables a su alrededor utilizando el análisis Big O.

+1

+1 wow. Nunca * nunca * he oído hablar de tal técnica. – poundifdef

+0

Esto realmente no merece +6, ya que no es nada heterodoxo y va en contra de la pregunta .......pero es razonable, así que tampoco puedo declinarte: \ – mpen

+4

@Mark: es una broma, diciendo que la forma perfectamente lógica de optimizar es "poco ortodoxa" porque pocas personas realmente lo hacen de esa manera. –

6

Duff's Device es el ejemplo canónico. Es tan extraño que Tom Duff admitió: "Este código forma algún tipo de argumento en [el debate sobre el fracaso en las declaraciones de casos], pero no estoy seguro de si es a favor o en contra".

4

Perfile su código, encuentre los puntos lentos y use el ensamblaje en línea para optimizarlos.

+2

Cuando trabajé en una compañía de juegos, lo hicimos. Pero eventualmente, se logran rendimientos decrecientes y hay que mirar el panorama general. A menudo descubrimos que reorganizar el diseño de las estructuras de datos afectaba en gran medida el rendimiento general. – Nosredna

+1

Olvidó el paso 4: perfil de nuevo para asegurarse de que su ensamblaje en línea en realidad no ralentizó el código. He visto que eso suceda. –

1
+0

Y Carmack's no es de Carmack. – Nosredna

+0

Correcto, pero se lo conoce como tal. – jason

+1

También suele ser una penalización de rendimiento * en la mayoría de los hardware modernos (ya que la mayoría de las arquitecturas tienen una instrucción de raíz cuadrada recíproca de hardware que permanece en el dominio FP). –

3

¿Está buscando una solución poco ortodoxa, sin restricciones, pero de uso general para la optimización de C?

Vuelva a escribirlo en el idioma del ensamblador.

3

1) Despliegue del bucle. Guarda un salto, compara e incrementa cada iteración si no bucleas realmente.
2) Evite la doble indirección. Por lo general, es más rápido realizar la recuperación aritmética, por lo que a [y * altura + x] suele ser más rápido que a [y] [x]. Además, una matriz unidimensional de tamaño MxN ahorra M (o N) palabras con valor de punteros en comparación con una matriz rectangular de dimensiones MxN.
3) Use optimizaciones de ensamblaje ridículas siempre que sea posible. Por ejemplo, en la arquitectura x86, puede usar la instrucción BSWAP para intercambiar bytes en una operación en lugar del patrón temp=a; a=b; b=temp; normal.

Y, por supuesto, no olvide:
4) No haga comprobaciones de límites o manejo de errores.

Habiendo dicho eso, evitaría todos estos excepto (2) en la práctica.

+1

Excepto que la mayoría de esto es inútil porque el compilador lo hará. –

+1

En la mayoría de los casos, las "optimizaciones poco ortodoxas" son inútiles: señalar que las respuestas a una pregunta inútil son en sí mismas inútiles es un poco ... inútil. (c: – Dathan

+0

¿No pueden los compiladores hacer automáticamente 1 y 2? ¿Y no debería haber una biblioteca llena de hacks de ensamblaje para este tipo de cosas? – mpen

1

Su compilador es casi seguro que mejor en la optimización de sus intentos feas le daría. La mayoría de los pequeños trucos históricos ahora son inútiles. Las personas que ignoran la legibilidad y la facilidad de mantenimiento tienden a escribir códigos que terminan siendo menos eficientes porque las optimizaciones reales se vuelven más difíciles.

Cuando el código se ha optimizado de todas las maneras posibles y aún necesita ganancia de rendimiento, la reescritura de las partes críticas en ASM es la mejor esperanza para tener algún efecto.

4

¿Usar el montaje en línea?

En serio, si con solo cambiar el código C puede mejorar el rendimiento, lo más probable es que pueda hacerlo limpiamente.

algunas excepciones:

1) Si se basa en la semántica de alineación para los punteros de diferentes tipos a menudo se pueden realizar operaciones de bloque de punteros que técnicamente exponer la aplicación a unos límites condición del sobrante, pero en la práctica no lo hace debido a las características de alineación de su sistema Para que se pueda realizar una copia de memoria, alineando los caracteres iniciales y luego el bloque interno se puede hacer usando un puntero largo *.

2) Es posible copiar estructuras de pila de manera inteligente si conoce el orden de memoria en el cual su compilador asigna variables locales. Esto puede permitirle implementar rutinas conjuntas que el idioma no admite. Las corutinas a menudo son una forma más simple y más rápida de implementar algunos tipos de control de bucle.

3) Los sindicatos siempre son un poco "hacky" como usted los use. Es una forma de implementar el polimorfismo con una comprobación de tipo bastante flexible.

4) El uso del preprocesador C como una forma de generar código automáticamente suele ser muy difícil de depurar y leer. Como tal, las personas tienden a evitar esto.

1

En las aplicaciones DSP, vale la pena ir al lenguaje ensamblador para obtener el mejor rendimiento de las instrucciones SIMD que los compiladores de C no hacen muy bien. Pero esa no es realmente una solución "C".

Algo que hago bastante a menudo es utilizar el software de ajuste de curva para reemplazar funciones con aproximaciones que son más rápidas de calcular. A veces, las LUT son más rápidas que hacer un montón de cálculos, pero no tan a menudo como solían ser.

1

Consulte este capítulo, It’s a plain Wonderful Life por Abrash (se trata de 5 páginas: haga clic en 'Siguiente' en la parte inferior de cada pantalla).

Resumen (algunas citas del artículo):

  • magia basada en tablas (enorme tabla de consulta y máquina de estados increíble)
  • Un enfoque de la programación rendimiento que funciona a un nivel más eficientes, altamente integrado de lo que puede ver jamás
  • economía asombroso de esfuerzo
1

No hay nada izquierda poco ortodoxo que hacer por el rendimiento del código C. Todas las técnicas efectivas han sido "ortodoxas".

Lo mejor que he encontrado es utilizar un generador de perfiles con acceso a los contadores de rendimiento de la CPU y prestar especial atención a la caché y las omisiones de la sucursal. Agregue las búsquedas previas de caché siempre que pueda y elimine las ramas impredecibles siempre que pueda.

No se moleste en desenrollar el lazo. Si la rama es predecible, es casi gratuita. Deje que el compilador se preocupe por eso.

En algunas arquitecturas muy paralelas como IA64, puede ser más rápido desenrollar un ciclo hasta el final. Un ejemplo de esto es evitar las funciones de cadena C. Use memset para poner a cero una matriz de cadenas, memcpy para establecer la cadena y memcmp para comparar toda la matriz con otra matriz similar. Esto puede usar cargas de 64 bits, nunca tiene que verificar el cero terminador y puede optimizarse para que no se bucle o bifurque si se usa un tamaño de matriz "pequeño" de 64 o 128. Las funciones de memxxx() generalmente se compilan con compilador- ins y muy optimizado.

2

Escucho muchas respuestas de la forma "Intente hacer X, Y o Z", pero eso es como decir "Oye, come pescado y come bien por un día".

Prefiero enseñarle a pescar, por problemas de rendimiento. Las personas que dicen "Perfil primero" están en el camino correcto, pero (en mi humilde opinión) son demasiado tímidas.

Here's an example of aggressive performance tuning.

Here's a short explanation of why it works.

Here's a long explanation of why it works.

que le enseñará a pescar, mostrando cómo averiguar donde están los peces y lo grandes que son. Una vez que los encuentre, puede cocinarlos (arreglarlos) de muchas maneras maravillosas. Lo bueno es que, una vez que encuentra y se deshace de un pez (problema de rendimiento), los otros se hacen más grandes y más fáciles de atrapar.

2

Para el punto 3 anterior, dentro de la respuesta de Dathan, otra forma de intercambio, puede intercambiar variables de forma no convencional utilizando xor.

 
int = 3, y = 4; 
x = x^y; 
y = y^x; 
x = x^y; 

Ahora xyy se intercambian! :)

Otra cosa, cuando se divide algo con 2, es mejor usar el operador de cambio a la derecha. Lo mismo podría decirse para multiplicar por 2, desplazar a la izquierda.

En el antiguo compilador de Borland C, había una propiedad _stklen que puede asignar para reducir el tamaño y el código de la pila. No he visto nada de eso hoy en día ya que la tecnología compiladora ha avanzado desde entonces.

Al usar malloc, sería mejor utilizar calllo en cambio, ya que inicializa la memoria a cero.

El uso del operador ternario en lugar de la instrucción if/else es aparentemente más rápido, supongo que los escritores de compiladores se han vuelto más inteligentes con respecto a la generación de código de máquina. Simplemente no puedo proporcionar una prueba de eso en ese sentido, pero se mantuvo cierto en aquel entonces cuando Borland C 3.01 gobernó el gallinero.

Código de alineación con rutinas de ensamblaje.

Me gusta este tema de la pregunta ya que me recuerda los viejos tiempos cuando la memoria era preciosa y tener que exprimir una pinta en un recipiente de cuarto de galón y utilizar los trucos de hocus pocus del código x86. Gracias por publicar esta pregunta Mr.Database.

Tenga cuidado, Tom.

+1

También debo mencionar que cuando se trata de matrices, es más rápido hacerlo usando * (some_array + n) cuando ha declarado char some_array [50] ... pero eso podría ser irrelevante ahora con respecto a la tecnología de compilación de hoy ...;) – t0mm13b

Cuestiones relacionadas