segunda-feira, 28 de dezembro de 2009

Processadores: entendendo a memória cache

Apesar de toda a evolução a memória RAM continua sendo muito mais lenta que o processador. O principal motivo disso é que a memória depende do processo de carga e descarga do capacitor onde é armazenado o impulso elétrico, uma operação cuja velocidade está mais ligada às leis da física do que à técnica de fabricação.

Com o passar do tempo, diversos truques foram usados para aumentar a velocidade efetiva dos módulos de memória, incluindo o uso de múltiplas transferências por ciclo, pré-ativação de células que serão usadas nas leituras seguintes e assim por diante. Entretanto, apesar de todos os esforços, os processadores continuam a evoluir mais rápido e a diferença tende apenas a aumentar.

Se o desempenho do processador fosse atrelado ao desempenho da memória RAM, os PCs teriam estagnado na época do 486, já que simplesmente não faria sentido desenvolver processadores mais rápidos, apenas para que eles passassem esperar mais e mais ciclos pelas leituras na memória. A solução veio com a introdução da memória cache, que serve como um reservatório temporário de dados com grande possibilidade de serem usados pelo processador, reduzindo a percentagem de vezes em que ele precisa buscar informações diretamente na memória.

Mesmo sendo muito pequeno em relação à memória, o cache acaba fazendo uma enorme diferença devido à maneira como os processadores trabalham. Diferente dos chipsets das placas 3D e de outros dispositivos que manipulam grandes volumes de dados, realizando operações relativamente simples, os processadores manipulam volumes de dados relativamente pequenos, executando operações complexas. Em resumo, o processador é como um matemático, que lê uma equação e fica algum tempo trabalhando nela antes de escrever o resultado. Com isso, mesmo um cache pequeno é capaz de melhorar o desempenho de maneira considerável.

Diferente de um simples buffer (como os usados em gravadores de CD para evitar que você perca a mídia por interrupções na gravação), onde os dados entram e saem na mesma ordem, o cache é um dispositivo bem mais inteligente, que além das células de memória, inclui um controlador que monitora o trabalho do processador, coletando blocos de informações que são frequentemente acessados e antecipando sempre que possível a leitura de dados que serão necessários nos ciclos seguintes.

Em um exemplo tosco, você pode imaginar uma lanchonete onde 10 dos lanches respondem por 90% dos pedidos. Em vez de esperarem que os clientes peçam para só então começar a preparar os pedidos, os atendentes poderiam começar a preparar os lanches mais solicitados com antecedência (estilo McDonald's) para que os clientes recebam os pedidos mais rapidamente. Nesse caso, o tempo de preparo continua o mesmo, mas a espera para os clientes se torna muito menor.

A diferença fundamental entre a memoria cache e a memória RAM é o tipo de célula usado. A memória cache é formada por células de memória SRAM, que são tipicamente formadas por conjuntos de 6 transístores, onde 4 deles formam a estrutura que mantém a carga e os outros dois controlam o acesso para leitura e gravação. Se você pudesse olhar um chip de memória SRAM com um microscópio de elétrons, veria uma estrutura similar a essa:



As células de memória SRAM são muito mais rápidas que as de memória RAM, mas são em compensação também muito mais caras, já que são necessários 6 transístores para cada bit de dados e mais um grande número de trilhas e circuitos adicionais. Em teoria, seria possível criar PCs que utilizassem apenas memória SRAM em vez de memória RAM, mas o custo seria proibitivo. Em vez disso, são usados pequenos blocos de cache, que graças a todas as otimizações acabam oferecendo 99% do ganho a 1% do custo.

O cache começou a ser usado na época do 386, onde o cache era opcional e fazia parte da placa-mãe. Ao lançar o 486, a Intel integrou um cache de 8 KB diretamente ao processador, que embora muito pequeno, era extremamente rápido, já que operava na mesma frequência que ele e oferecia baixos tempos de latência. O cache incluído no processador passou então a ser chamado de cache L1 (nível 1) e o cache na placa-mãe passou a ser chamado de cache L2 (ou cache secundário).



Sempre que precisa de novas informações, o processador checa primeiro as informações disponíveis no cache L1. Caso não encontre o que precisa, ele verifica em seguida o cache L2 e por último a memória. Sempre que o processador encontra o que precisa nos caches temos um "cache hit" e sempre que precisa recorrer à memória temos um "cache miss". Quanto maior a percentagem de cache hits, melhor é o desempenho.

O cache na placa-mãe continuou a ser usado até a época das placas soquete 7, mas ele foi se tornando cada vez mais ineficiente conforme os processadores passaram a usar multiplicadores de clock mais altos. O motivo é simples: instalado na placa-mãe, o cache L2 opera sempre na mesma frequência que ela (66 ou 100 MHz na época), enquanto o cache L1 operava na mesma frequência do processador.

Com a introdução das memórias SDRAM e mais tarde das DDR, a diferença de desempenho entre a memória e o cache passou a ser relativamente pequena, tornando os ganhos de desempenho cada vez menores. Isso levou a Intel a incorporar o cache L2 diretamente no processador a partir do Pentium Pro, abandonando o uso de cache na placa-mãe.

Inicialmente o cache L2 era um chip separado, que dividia o encapsulamento com o processador, mas a partir da segunda geração do Celeron (e do Pentium III Coppermine) ele passou a ser integrado diretamente ao processador, o que reduziu os tempos de acesso e também os custos.

Esta é uma foto do núcleo de um Pentium III Coppermine com seus 256 KB de cache L2 integrado, que são representados pelos 16 retângulos na parte inferior do processador. Você pode notar que o cache L2 ocupa uma área significativa do núcleo do processador, o que explica o fato de serem usados apenas 256 KB:



O cache L2 integrado foi adotado em todos os processadores daí em diante, do Athlon Thunderbird ao Core 2 Quad. Existem diferenças entre os caches usados pela Intel e a AMD (a Intel usa um cache inclusivo, enquanto a AMD usa um cache exclusivo, entre outras diferenças), mas em ambos os casos os papéis dos cache L1 e L2 são bem similares.

O cache L1 é sempre muito pequeno (de 32 a 128 KB) e oferece tempos de acesso muito baixos, equivalentes a apenas 3 ou 4 ciclos (o que em um processador de 3.0 GHz equivale a apenas 1 ou 1.33 nanossegundos). Entretanto, todo esse desempenho tem um custo, que é a necessidade de usar células com mais transístores, controladores mais sofisticados e mais trilhas de acesso, o que torna o cache L1 muito caro em termos de transístores usados.

O cache L2 por sua vez é baseado no uso de células mais lentas, com controladores mais simples e menos linhas de dados. Isso permite que o cache L2 seja sempre muito maior (de 256 KB a 2 MB), mas ele em compensação trabalha com tempos de acesso mais altos, de tipicamente 10 a 15 ciclos.

Embora possa soar estranha à primeira vista, essa relação é a que oferece o melhor custo-benefício na maioria dos casos, já que o bom desempenho do cache L1 permite que o processador tenha acesso rápido aos dados na maioria dos casos e o grande cache L2 serve como uma segunda parada para os casos em que ele não encontra o que precisa no L1.

Os processadores atuais usam controladores de cache bastante avançados, o que permite que os caches trabalhem com percentagens de acerto surpreendentemente boas considerando o tamanho. Tipicamente, o cache L1 responde por 80% dos acessos, o cache L2 responde por mais 18 ou 19% e a memória RAM responde pelos 1 ou 2% restantes. À primeira vista, pode parecer que não vale à pena sacrificar um espeço tão grande no processador para adicionar um grande cache L2 que responde por menos de 20% dos acessos, mas se fizermos as contas podemos ver que ele é bem importante.

Tomando como exemplo um processador onde o cache L1 trabalha com tempos de acesso de 3 ciclos, o cache L2 trabalha com 15 ciclos e a memória RAM com 140 ciclos e os caches respondem por respectivamente 80% e 19% dos acessos, teríamos a seguinte relação depois de 1 milhão de acessos:

Cache L1 (80%): 2.400.000 ciclos
Cache L2 (19%): 2.850.000 ciclos
Memória (1%): 1.400.000 ciclos
Total: 6.650.000 ciclos

Você pode notar que mesmo respondendo por uma pequena parcela dos acessos, a memória RAM é responsável por um volume desproporcionalmente grande de ciclos de espera. Um aumento de apenas 1% na percentagem de acessos à memória causaria uma verdadeira tragédia, elevando o total no exemplo para mais de 8 milhões de ciclos.

É justamente por isso que processadores com caches maiores ou com controladores de memória integrados (latência mais baixa) oferecem muitas vezes ganhos de desempenho de 10% ou mais em relação aos antecessores. Da mesma maneira, um cache L1 maior ou mais rápido pode fazer uma grande diferença, mas apenas se o aumento não for às custas de uma redução no cache L2, já que pouco adianta melhorar o desempenho do cache L1 em uma ponta, se o processador vai perder bem mais tempo acessando à memória na outra.

A divisão tradicional entre cache L1 e cache L2 funcionou bem durante a fase dos processadores single-core e dual-core. Entretanto, com a introdução dos processadores quad-core passou a fazer mais sentido usar caches L1 e L2 menores e incluir um terceiro nível de cache. Com isso, temos 4 pequenos blocos de cache L1 e L2 (um para cada núcleo) e um grande cache L3 compartilhado entre todos.

Um bom exemplo é o Core i7 de 45 nm, que usa 64 KB de cache L1 e 256 KB de cache L2 por núcleo e usa um grande cache L3 de 8 MB compartilhado entre todos. Dentro do processador, ele corresponde à área sombreada no diagrama a seguir, novamente uma área considerável:



Em micros antigos os caches se limitavam a armazenar as últimas informações acessadas, guardando cópias de dados acessados pelo processador e descartando as informações mais antigas ou menos acessadas. Os cache atuais são bem mais eficientes, incorporando algoritmos bem mais eficientes e sistemas de prefetch, que monitoram o fluxo de instruções e carregam antecipadamente dados que serão necessários nos ciclos seguintes. Desde o Pentium, o cache é também capaz de acelerar as operações de gravação, permitindo que o processador grave os dados diretamente no cache, deixando que o controlador se encarregue de gravá-los na memória posteriormente.

Outra curiosidade é que os primeiros processadores usavam caches unificados, que não faziam distinção entre dados e instruções, tratando ambos com o mesmo nível de prioridade. A partir do Pentium Pro, o cache L1 passou a ser dividido em dois blocos independentes, um para dados e outro para instruções. Essa divisão permite que o controlador de cache use o espaço de forma mais eficiente e melhora a velocidade de acesso, já que os dois blocos passam a se comportar como dois caches independentes, permitindo que o processador leia dados e instruções simultaneamente.

Além dos caches, os processadores incluem também um TLB (Translation lookaside buffer), que armazena endereços de memória, convertendo os endereços lógicos usados pelos aplicativos em execução nos endereços físicos nos chips de memória. O TLB é um circuito bem mais simples que os caches e é posicionados entre o cache L2 (ou L3) e a memória RAM.

Cada aplicativo (ou mais especificamente cada processo em execução) acha que tem à disposição um bloco contínuo de endereços de memória, enquanto na verdade está utilizando endereços espalhados por vários chips ou mesmo módulos de memória diferentes (ou até memória swap em alguns casos). Com isso, sempre que o processador precisa ler informações diretamente na memória RAM, precisa primeiro converter os endereços usados pelo aplicativo nos endereços físicos da memória onde eles estão armazenados, verificando a entrada correspondente no TLB.

Sem o TLB, o processador precisaria fazer uma longa busca sequencial, pesquisando uma a uma as páginas de endereços da memória até encontrar os endereços correspondentes (um processo extremamente demorado), antes mesmo de iniciar o acesso propriamente dito.

Diferente dos caches, o TLB funciona como um buffer, que simplesmente armazena endereços em uso. Ele é um daqueles recursos que todos tomam como certo e que só recebe atenção quando algo dá errado, como no infame TLB Bug, que afetou as versões iniciais do Phenom, prejudicando o desempenho.


Fonte: http://www.gdhpress.com.br/blog/entendendo-cache/

Gostou? Então copie esse post para seu Blog:

1 comentários:

O.G disse...

Muito bom cara... parabéns !!!