Otimizações genéricas
Aqui estão algumas das minhas otimizações favoritas. Na verdade, aumentei os tempos de execução e reduzi os tamanhos dos programas usando isso.
Declarar pequenas funções como inline
ou macros
Cada chamada a uma função (ou método) incorre em sobrecarga, como colocar variáveis na pilha. Algumas funções também podem gerar uma sobrecarga no retorno. Uma função ou método ineficiente tem menos instruções em seu conteúdo do que a sobrecarga combinada. Esses são bons candidatos para inlining, seja como #define
macros ou inline
funções. (Sim, eu sei que inline
é apenas uma sugestão, mas neste caso eu considero um lembrete para o compilador).
Remova o código morto e redundante
Se o código não for usado ou não contribuir para o resultado do programa, livre-se dele.
Simplifique o design de algoritmos
Certa vez, removi muito código de montagem e tempo de execução de um programa, escrevendo a equação algébrica que ele estava calculando e, em seguida, simplifiquei a expressão algébrica. A implementação da expressão algébrica simplificada ocupou menos espaço e tempo do que a função original.
Loop Unrolling
Cada loop tem uma sobrecarga de incremento e verificação de término. Para obter uma estimativa do fator de desempenho, conte o número de instruções no overhead (mínimo 3: incremento, verificação, vá para o início do loop) e divida pelo número de instruções dentro do loop. Quanto menor o número, melhor.
Editar: forneça um exemplo de desenrolamento de loop Antes:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Após desenrolar:
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++;
}
Com essa vantagem, um benefício secundário é obtido: mais instruções são executadas antes que o processador precise recarregar o cache de instruções.
Tive resultados incríveis quando desenrolei um loop para 32 declarações. Esse foi um dos gargalos, pois o programa teve que calcular uma soma de verificação em um arquivo de 2 GB. Essa otimização combinada com a leitura de blocos melhorou o desempenho de 1 hora para 5 minutos. O desenrolamento de loops também forneceu um desempenho excelente em linguagem assembly, mas memcpy
foi muito mais rápido que o compilador memcpy
. - TM
Redução de if
declarações
Os processadores detestam desvios ou saltos, uma vez que força o processador a recarregar sua fila de instruções.
Aritmética booleana ( editado: formato de código aplicado ao fragmento de código, exemplo adicionado)
Converta if
instruções em atribuições booleanas. Alguns processadores podem executar instruções condicionalmente sem ramificação:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
O curto-circuito do operador lógico AND ( &&
) impede a execução dos testes se o status
for false
.
Exemplo:
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;
};
Alocação de variável de fator fora dos loops
Se uma variável for criada rapidamente dentro de um loop, mova a criação / alocação para antes do loop. Na maioria dos casos, a variável não precisa ser alocada durante cada iteração.
Fatore as expressões constantes fora dos loops
Se um cálculo ou valor de variável não depende do índice do loop, mova-o para fora (antes) do loop.
I / O em blocos
Leia e grave dados em grandes blocos (blocos). Quanto maior melhor. Por exemplo, ler um octeto por vez é menos eficiente do que ler 1.024 octetos com uma leitura.
Exemplo:
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);
A eficiência desta técnica pode ser demonstrada visualmente. :-)
Não use printf
família para dados constantes
Dados constantes podem ser produzidos usando uma gravação de bloco. A gravação formatada perderá tempo examinando o texto em busca de caracteres de formatação ou processando comandos de formatação. Veja o exemplo de código acima.
Formate na memória e depois escreva
Formate em uma char
matriz usando vários e sprintf
, em seguida, use fwrite
. Isso também permite que o layout de dados seja dividido em "seções constantes" e seções variáveis. Pense em mala direta .
Declare texto constante (literais de string) como static const
Quando as variáveis são declaradas sem o static
, alguns compiladores podem alocar espaço na pilha e copiar os dados da ROM. Essas são duas operações desnecessárias. Isso pode ser corrigido usando o static
prefixo.
Por último, o código como o compilador faria
Às vezes, o compilador pode otimizar várias pequenas instruções melhor do que uma versão complicada. Além disso, escrever código para ajudar a otimizar o compilador também ajuda. Se eu quiser que o compilador use instruções especiais de transferência de bloco, vou escrever um código que parece que deve usar as instruções especiais.