Optimizaciones genéricas
Aquí algunas de mis optimizaciones favoritas. De hecho, he aumentado los tiempos de ejecución y reducido el tamaño de los programas al usarlos.
Declarar pequeñas funciones como inline
macros
Cada llamada a una función (o método) genera una sobrecarga, como insertar variables en la pila. Algunas funciones también pueden incurrir en gastos generales a la devolución. Una función o método ineficaz tiene menos declaraciones en su contenido que la sobrecarga combinada. Estos son buenos candidatos para la inserción, ya sea como #define
macros o inline
funciones. (Sí, sé que inline
es solo una sugerencia, pero en este caso lo considero como un recordatorio para el compilador).
Elimina el código muerto y redundante
Si el código no se utiliza o no contribuye al resultado del programa, elimínelo.
Simplifique el diseño de algoritmos
Una vez eliminé mucho código ensamblador y tiempo de ejecución de un programa escribiendo la ecuación algebraica que estaba calculando y luego simplifiqué la expresión algebraica. La implementación de la expresión algebraica simplificada ocupó menos espacio y tiempo que la función original.
Desenrollado de bucle
Cada bucle tiene una sobrecarga de comprobación de incrementos y terminaciones. Para obtener una estimación del factor de rendimiento, cuente el número de instrucciones en la sobrecarga (mínimo 3: incremento, verificación, ir al inicio del ciclo) y divida por el número de declaraciones dentro del ciclo. Cuanto menor sea el número, mejor.
Editar: proporcione un ejemplo de desenrollado de bucle Antes:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Después de desenrollar:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
En esta ventaja, se obtiene un beneficio secundario: se ejecutan más sentencias antes de que el procesador tenga que recargar la caché de instrucciones.
Tuve resultados asombrosos cuando desenrollé un ciclo a 32 declaraciones. Este fue uno de los cuellos de botella ya que el programa tuvo que calcular una suma de verificación en un archivo de 2GB. Esta optimización combinada con la lectura de bloques mejoró el rendimiento de 1 hora a 5 minutos. El desenrollado de bucles también proporcionó un rendimiento excelente en lenguaje ensamblador, mi memcpy
fue mucho más rápido que el del compilador memcpy
. - TM
Reducción de if
declaraciones
Los procesadores odian las ramificaciones o saltos, ya que obliga al procesador a recargar su cola de instrucciones.
Aritmética booleana ( Editado: formato de código aplicado al fragmento de código, ejemplo agregado)
Convertir if
declaraciones en asignaciones booleanas. Algunos procesadores pueden ejecutar instrucciones de forma condicional sin ramificar:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
El cortocircuito del operador lógico AND (&&
) impide la ejecución de las pruebas si status
es false
.
Ejemplo:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
Factorizar la asignación de variables fuera de los bucles
Si una variable se crea sobre la marcha dentro de un ciclo, mueva la creación / asignación antes del ciclo. En la mayoría de los casos, no es necesario asignar la variable durante cada iteración.
Factorizar expresiones constantes fuera de los bucles
Si un cálculo o valor de variable no depende del índice del ciclo, muévalo fuera (antes) del ciclo.
E / S en bloques
Leer y escribir datos en grandes porciones (bloques). Cuanto más grande, mejor. Por ejemplo, leer un octecto a la vez es menos eficiente que leer 1024 octetos con una lectura.
Ejemplo:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
La eficacia de esta técnica se puede demostrar visualmente. :-)
No use la printf
familia para datos constantes
Los datos constantes se pueden generar mediante una escritura en bloque. La escritura formateada perderá tiempo escaneando el texto en busca de caracteres de formato o comandos de formato de procesamiento. Vea el ejemplo de código anterior.
Formatee en la memoria, luego escriba
Formatee en una char
matriz usando multiple sprintf
, luego use fwrite
. Esto también permite dividir el diseño de datos en "secciones constantes" y secciones variables. Piense en la combinación de correspondencia .
Declare texto constante (cadenas literales) como static const
Cuando las variables se declaran sin el static
, algunos compiladores pueden asignar espacio en la pila y copiar los datos de la ROM. Estas son dos operaciones innecesarias. Esto se puede arreglar usando el static
prefijo.
Por último, código como lo haría el compilador
A veces, el compilador puede optimizar varias declaraciones pequeñas mejor que una versión complicada. Además, escribir código para ayudar al compilador a optimizar también ayuda. Si quiero que el compilador use instrucciones especiales de transferencia en bloque, escribiré un código que parece que debería usar las instrucciones especiales.