videos quitar publicitaria publicidad los eliminar desactivar cuenta como comercial cancelar anuncios anuncio administrador c optimization

quitar - eliminar publicidad facebook android



¿Qué tipos de prácticas de optimización de C en x86 que históricamente se recomendaron usar ya no son efectivas? (4)

De las optimizaciones que se recomiendan comúnmente, una pareja que básicamente nunca es fructífera dado a los compiladores modernos incluyen:

Transformaciones matematicas

Los compiladores modernos entienden las matemáticas y realizarán transformaciones en las expresiones matemáticas cuando sea apropiado.

Las optimizaciones, como la conversión de la multiplicación a la suma, o la multiplicación constante o la división a los cambios de bits, ya las realizan los compiladores modernos, incluso a niveles de optimización bajos. Ejemplos de estas optimizaciones incluyen:

x * 2 -> x + x x * 2 -> x << 1

Tenga en cuenta que algunos casos específicos pueden diferir. Por ejemplo, x >> 1 no es lo mismo que x / 2 ; ¡No es apropiado sustituir uno por el otro!

Además, muchas de estas optimizaciones sugeridas no son en realidad más rápidas que el código que reemplazan.

Trucos de código estúpido

Ni siquiera estoy seguro de cómo llamar esto, pero los trucos como el intercambio XOR ( a ^= b; b ^= a; a ^= b; ) no son optimizaciones en absoluto. Solo son trucos para fiestas, son más lentos y más frágiles que el enfoque obvio. No los uses.

La palabra clave de register

Esta palabra clave es ignorada por muchos compiladores modernos, ya que su significado intencionado (forzar que una variable se almacene en un registro) no es significativo dados los algoritmos actuales de asignación de registros.

Transformaciones de código

Los compiladores realizarán automáticamente una amplia variedad de transformaciones de código cuando sea apropiado. Varias de estas transformaciones que se recomiendan con frecuencia para la aplicación manual, pero que rara vez son útiles cuando se aplican, incluyen:

  • Loop desenrollado. (Esto suele ser realmente perjudicial cuando se aplica de forma indiscriminada, ya que aumenta el tamaño del código).
  • Función inlining. (Etiquete una función como static , y generalmente estará en línea donde sea apropiado cuando la optimización esté habilitada).

Debido a los avances en los compiladores de x86 C (a saber, GCC y Clang), muchas prácticas de codificación que se creían que mejoraban la eficiencia ya no se usan, ya que los compiladores pueden hacer un mejor trabajo optimizando el código que los humanos (por ejemplo, cambio de bits vs. multiplicación ).

¿Qué prácticas específicas son estas?


Debido a la pluralidad de plataformas, lo mejor sería optimizar para una plataforma determinada (o arquitectura / modelo de CPU) y compilador. Si su código se ejecuta en muchas plataformas, es una pérdida de tiempo. (Estoy hablando de micro opciones, siempre vale la pena considerar mejores algoritmos)

Dicho esto, optimizando para una plataforma dada, DSP tiene sentido si surge la necesidad. Entonces, el mejor primer ayudante es, en mi humilde opinión, el uso juicioso de la palabra clave restrict si el compilador / optimizador lo soporta bien. Evite los algoritmos que involucran condiciones y código de salto (saltos, goto, si, tiempo, ...) Esto favorece la transmisión y evita demasiadas predicciones erróneas de ramas. Estoy de acuerdo en que estas sugerencias son de sentido común ahora.

En términos generales, diría: cualquier manipulación que modifique el código al hacer suposiciones sobre cómo optimiza el compilador se evitará en absoluto.

Más bien, luego cambie al ensamblaje (práctica común para algunos algoritmos realmente importantes en DSPs donde los compiladores, aunque son realmente geniales, aún se pierden el último% de los ciclos de CPU / Mem en el aumento de rendimiento ...)


Una de estas prácticas es evitar las multiplicaciones mediante el uso de matrices de punteros de matriz en lugar de matrices 2D reales.

Antigua práctica

int width = 1234, height = 5678; int* buffer = malloc(width*height*sizeof(*buffer)); int** image = malloc(height*sizeof(*image)); for(int i = height; i--; ) image[i] = &buffer[i*width]; //Now do some heavy computations with image[y][x].

Esto solía ser más rápido, ya que las multiplicaciones solían ser muy caras (del orden de 30 ciclos de CPU), mientras que los accesos a la memoria eran prácticamente gratuitos (fue solo en la década de 1990 que se agregaron cachés porque la memoria no podía mantenerse al día con la CPU completa) velocidad).

Pero las multiplicaciones se hicieron más rápidas, algunas CPU pudieron realizarlas en un ciclo de CPU, mientras que los accesos a la memoria no se mantuvieron al ritmo. Entonces, ahora es probable que este código sea más eficaz:

int width = 1234, height = 5678; int (*image)[width] = malloc(height*sizeof(*image)); //Now do some heavy computations with image[y][x], //which will invoke pointer arithmetic to calculate the offset as (y*width + x)*sizeof(int).

Actualmente, todavía hay algunas CPU, donde el segundo código no es más rápido, pero la gran penalización de la multiplicación ya no está con nosotros.


Una optimización que realmente no debería usarse mucho más es #define (ampliando un poco la respuesta de duskwuff).

El preprocesador de C es una cosa maravillosa, y puede hacer algunas transformaciones de código sorprendentes, y puede hacer que ciertos códigos realmente complejos sean mucho más sencillos, pero usar #define solo para hacer que una pequeña operación esté en línea ya no es apropiado. La mayoría de los compiladores modernos tienen una palabra clave en inline real (o equivalente, como __inline__ ), y son lo suficientemente inteligentes como para integrar la mayoría de las funciones static , lo que significa que el código es el siguiente:

#define sum(x, y) ((x) + (y))

es realmente mejor escrito como la función equivalente:

static int sum(int x, int y) { return x + y; }

Evita los peligrosos problemas de evaluación múltiple y los efectos secundarios, obtiene una verificación de tipo del compilador y también obtiene un código más limpio. Si vale la pena incorporarlo, el compilador lo hará.

En general, guarde el preprocesador para las circunstancias en las que sea necesario: emita una gran cantidad de códigos complejos, variantes o códigos parciales rápidamente. El uso del preprocesador para incluir funciones pequeñas y definir constantes es ahora en su mayoría antipatrón.