Cache de dados em C++: quando a memória é mais rápida que o disco
Imagine que sua aplicação C++ é um bibliotecário. Cada vez que um leitor pede um livro, o bibliotecário corre para um depósito distante (leia-se: banco de dados ou sistema de arquivos). Isso é lento. Mas se o bibliotecário começar a manter os livros mais populares em sua mesa (na memória RAM), a velocidade de entrega aumenta drasticamente. Isso é o cache de dados em C++.
Hoje vamos falar sobre como transformar sua aplicação de uma tartaruga em um guepardo. Analisaremos não apenas a teoria, mas também escreveremos código real. Você aprenderá quais estratégias de cache existem, como não dar um tiro no pé ao trabalhar com memória e onde encontrar soluções prontas para não reinventar a roda.
Por que o cache é indispensável para o desenvolvedor C++?
C++ é uma linguagem para sistemas de alta carga. Robôs de negociação, engines de jogos, servidores de banco de dados — em todos os lugares onde cada microssegundo importa, o cache se torna uma salvação. Sem ele, você estará constantemente esbarrando em I/O (entrada/saída) ou em recálculos repetidos.
Principais benefícios da implementação de cache:
- Redução do tempo de resposta — dados da RAM são lidos 100.000 vezes mais rápido que do HDD.
- Diminuição da carga no banco de dados — menos requisições = menos latência.
- Economia de recursos computacionais — não é necessário recalcular funções pesadas.
Mas também existem armadilhas: vazamento de memória, dados desatualizados (stale data) e quedas de desempenho com implementação incorreta. Portanto, vamos entender passo a passo.
Principais estratégias de cache em C++
A escolha da estratégia depende de quais dados você está armazenando em cache e com que frequência eles mudam. Vamos analisar as três mais populares.
1. LRU (Least Recently Used) — removemos o que não foi acessado há mais tempo
Isso é um clássico. Imagine que você tem uma caixa para 10 livros. Quando um novo chega e a caixa está cheia, você joga fora o livro que não foi lido há mais tempo. O cache LRU em C++ funciona da mesma forma.
Implementação em C++ usando std::list e std::unordered_map:
#include <iostream>#include <list>#include <unordered_map>#include <string>
template <typename K, typename V>class LRUCache {private: size_t capacity; std::list<std::pair<K, V>> items; std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> cache;
public: LRUCache(size_t cap) : capacity(cap) {}
V get(const K& key) { auto it = cache.find(key); if (it == cache.end()) { return V(); // Retorna default se a chave não for encontrada } // Move o elemento para o início da lista (o mais recente) items.splice(items.begin(), items, it->second); return it->second->second; }
void put(const K& key, const V& value) { auto it = cache.find(key); if (it != cache.end()) { // Atualiza o elemento existente it->second->second = value; items.splice(items.begin(), items, it->second); return; }
if (cache.size() >= capacity) { // Remove o elemento mais antigo (último da lista) auto last = items.back(); cache.erase(last.first); items.pop_back(); }
// Insere o novo elemento no início items.emplace_front(key, value); cache[key] = items.begin(); }};
int main() { LRUCache<std::string, int> lru(3); lru.put("one", 1); lru.put("two", 2); lru.put("three", 3); lru.get("one"); // Acessa "one" — ele se torna recente lru.put("four", 4); // "two" será removido, pois não foi acessado há mais tempo