C++: Otimização de Desempenho

Última atualização: 2026-08-26

Na aula 48, aprendemos sobre os novos recursos do C++17/20.

Agora, vamos aprender sobre otimização de desempenho — fazendo programas C++ rodarem mais rápido.

A vantagem do C++ é o desempenho, mas usá-lo bem não é fácil.


1. Visão Geral da Otimização de Desempenho

(1) 1.1 Por Que Otimizar?

Princípios:

  1. Não otimize prematuramente (Premature Optimization)
  2. Meça primeiro, depois otimize
  3. Otimize o algoritmo primeiro, depois o código

(2) 1.2 Gargalos de Desempenho

Gargalo Proporção
Algoritmo 70%
Acesso à memória 20%
Outros 10%

Conclusão: Otimizar o algoritmo é mais importante do que otimizar o código.



2. Alinhamento de Memória

(1) 2.1 O Que É Alinhamento de Memória?

Alinhamento de memória significa que os dados são armazenados em endereços de memória que são múltiplos de um determinado valor (geralmente uma potência de 2).

Por que é importante?


(2) 2.2 Exemplo: Impacto do alinhamento de memória (Dificuldade ⭐⭐)

▶ Exemplo 1: Demo de programação orientada a objetos (Dificuldade ⭐)

CPP
#include <iostream>

struct BadAlignment {
 char c; // 1 byte
 int i; // 4 bytes (pode ser alinhado ao offset 4)
};

struct GoodAlignment {
 int i; // 4 bytes
 char c; // 1 byte
};

int main() {
 std::cout << "Bad: " << sizeof(BadAlignment) << " bytes" << std::endl;
 std::cout << "Good: " << sizeof(GoodAlignment) << " bytes" << std::endl;
 
 return 0;
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Bad: 8 bytes
Good: 8 bytes

Possível resultado de execução:

TEXT 📖 Somente leitura
Bad: 8 bytes
Good: 8 bytes

💡 Dica:



3. Amigabilidade ao Cache

(1) 3.1 Cache da CPU

O cache da CPU é 100 vezes mais rápido que a memória principal.

Princípios de otimização:

  1. Princípio da localidade: Acessar memória adjacente
  2. Acesso sequencial: Mais rápido que acesso aleatório
  3. Evitar cache misses: Minimizar acesso a memória não contígua

(2) 3.2 Exemplo: Ordem por linhas vs por colunas (Dificuldade ⭐⭐⭐)

CPP
#include <iostream>
### ▶ Exemplo 2: Uso de contêineres STL (Dificuldade ⭐)

#include <vector>
#include <chrono>

int main() {
 const int N = 1000;
 std::vector<std::vector<int>> matrix(N, std::vector<int>(N));
 
 // Ordem por linhas (amigável ao cache)
 auto start = std::chrono::high_resolution_clock::now();
 for (int i = 0; i < N; i++) {
 for (int j = 0; j < N; j++) {
 matrix[i][j] = 1;
 }
 }
 auto end = std::chrono::high_resolution_clock::now();
 auto row_time = std::chrono::duration<double>(end - start).count();
 
 // Ordem por colunas (não amigável ao cache)
 start = std::chrono::high_resolution_clock::now();
 for (int j = 0; j < N; j++) {
 for (int i = 0; i < N; i++) {
 matrix[i][j] = 1;
 }
 }
 end = std::chrono::high_resolution_clock::now();
 auto col_time = std::chrono::duration<double>(end - start).count();
 
 std::cout << "Row-major time: " << row_time << " seconds" << std::endl;
 std::cout << "Column-major time: " << col_time << " seconds" << std::endl;
 
 return 0;
}

Saída:

TEXT 📖 Somente leitura
Row-major time: 0.001 seconds
Column-major time: 0.003 seconds


4. Otimização do Compilador

(1) 4.1 Níveis de Otimização

Nível Flag Descrição
O0 Nenhum Sem otimização (para depuração)
O1 -O1 Otimização básica
O2 -O2 Recomendado (equilibrado)
O3 -O3 Otimização agressiva
Os -Os Otimizar por tamanho

(2) 4.2 Exemplo: Efeito da otimização do compilador (Dificuldade ⭐)

CPP
#include <iostream>

int main() {
 int sum = 0;
 for (int i = 0; i < 1000000; i++) {
 sum += i;
 }
 std::cout << sum << std::endl;
 
 return 0;
}

Saída:

TEXT 📖 Somente leitura
499999500000

Compilação:

BASH
g++ -O0 main.cpp # Lento
g++ -O2 main.cpp # Rápido (compilador pode calcular o resultado diretamente)


5. Ferramentas de Profiling

(1) 5.1 Ferramentas Comuns

Ferramenta Plataforma Descrição
gprof Linux Profiler do GCC
perf Linux Ferramenta de análise de desempenho do Linux
Valgrind Multiplataforma Análise de memória
Visual Studio Profiler Windows Integrado no VS

(2) 5.2 Exemplo: Medindo tempo com chrono (Dificuldade ⭐)

CPP
#include <iostream>
#include <chrono>

int main() {
 auto start = std::chrono::high_resolution_clock::now();
 
 // Código a medir
 long sum = 0;
 for (int i = 0; i < 100000000; i++) {
 sum += i;
 }
 
 auto end = std::chrono::high_resolution_clock::now();
 auto duration = std::chrono::duration<double>(end - start).count();
 
 std::cout << "Time elapsed: " << duration << " seconds" << std::endl;
 
 return 0;
}

Saída:

TEXT 📖 Somente leitura
Time elapsed: 0.05 seconds


6. Resumo de Técnicas de Otimização

(1) 6.1 Nível de Código

Técnica Descrição
Usar semântica de movimentação Reduzir cópias
Usar emplace_back Evitar objetos temporários
Usar reserve Reduzir realocação do vector
Usar unordered_map Tabela hash, busca O(1)

(2) 6.2 Nível de Algoritmo

Técnica Descrição
Escolher a estrutura de dados certa vector vs list vs map
Usar algoritmos apropriados sort vs partial_sort
Evitar cópias desnecessárias Usar referências, move

▶ Exemplo 3: Comparação de desempenho passagem por valor vs referência (Dificuldade ⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

struct BigData {
    std::vector<int> data;
    BigData() : data(10000, 0) {}
};

// Passagem por valor (cópia)
void processByValue(BigData d) {
    (void)d;
}

// Passagem por referência (sem cópia)
void processByRef(const BigData& d) {
    (void)d;
}

int main() {
    BigData big;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        processByValue(big);
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Pass by value time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;

    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        processByRef(big);
    }
    end = std::chrono::high_resolution_clock::now();
    std::cout << "Pass by reference time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;

    return 0;
}
▶ Experimente

Saída:

TEXT 📖 Somente leitura
Pass by value time: 0.15 seconds
Pass by reference time: 0.001 seconds

❓ Perguntas Frequentes

P: A otimização é sempre eficaz? R: Não necessariamente. Meça primeiro, encontre o gargalo, depois otimize.


P: C++ é sempre mais rápido que Python? R: Não necessariamente. Se o algoritmo for o mesmo, C++ geralmente é mais rápido. Mas com um algoritmo ruim, C++ também pode ser lento.


P: Como determino onde está o gargalo? R: Use um profiler.


📖 Resumo

Ponto-Chave Resumo
Alinhamento de memória Reduzir padding
Amigabilidade ao cache Acesso sequencial, localidade
Otimização do compilador -O2 recomendado
Profiling Meça primeiro, depois otimize
Técnicas de otimização Semântica de movimentação, emplace_back

📝 Exercícios

  1. Básico (Dificuldade ⭐): Armazene 100.000 inteiros usando vector e list, e compare a diferença de tempo para inserção no final e acesso aleatório.

  2. Intermediário (Dificuldade ⭐⭐): Teste a diferença de desempenho entre passagem por valor e passagem por referência. Escreva uma função que processa um struct grande (contendo vector<int>(10000)), medindo o tempo para passagem por valor e por referência const.

  3. Desafio (Dificuldade ⭐⭐⭐): Use cronometragem de alta precisão do std::chrono para comparar as diferenças de desempenho entre um loop for, o for_each do STL e o for baseado em range ao percorrer. Analise os resultados.



Próxima aula: Testes Unitários (#50)

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%