Projetar um processador para oferecer alto desempenho é muito mais do que apenas aumentar a taxa de clock. Existem inúmeras outras maneiras de aumentar o desempenho, possibilitadas pela lei de Moore e instrumentais para o design de processadores modernos.
As taxas do relógio não podem aumentar indefinidamente.
À primeira vista, pode parecer que um processador simplesmente execute um fluxo de instruções uma após a outra, com aumentos de desempenho atingidos por taxas de clock mais altas. No entanto, aumentar a taxa de clock por si só não é suficiente. O consumo de energia e a produção de calor aumentam à medida que o clock aumenta.
Com taxas de clock muito altas, é necessário um aumento significativo na tensão do núcleo da CPU . Como o TDP aumenta com o quadrado do núcleo V , chegamos a um ponto em que o consumo excessivo de energia, a produção de calor e os requisitos de refrigeração impedem novos aumentos na taxa de clock. Esse limite foi atingido em 2004, nos dias do Pentium 4 Prescott . Embora as recentes melhorias na eficiência de energia tenham ajudado, aumentos significativos na taxa de clock não são mais viáveis. Veja: Por que os fabricantes de CPU pararam de aumentar a velocidade de clock de seus processadores?
Gráfico das velocidades do relógio de estoque em PCs de ponta entusiasta ao longo dos anos. Fonte da imagem
- Através da lei de Moore , uma observação que afirma que o número de transistores em um circuito integrado dobra a cada 18 a 24 meses, principalmente como resultado do encolhimento da matriz , uma variedade de técnicas que aumentam o desempenho foram implementadas. Essas técnicas foram refinadas e aperfeiçoadas ao longo dos anos, permitindo que mais instruções sejam executadas durante um determinado período de tempo. Essas técnicas são discutidas abaixo.
Fluxos de instruções aparentemente sequenciais geralmente podem ser paralelizados.
- Embora um programa possa simplesmente consistir em uma série de instruções para executar uma após a outra, essas instruções, ou partes delas, podem frequentemente ser executadas simultaneamente. Isso é chamado de paralelismo no nível da instrução (ILP) . A exploração do ILP é vital para obter alto desempenho, e os processadores modernos usam inúmeras técnicas para fazê-lo.
O pipelining divide as instruções em pedaços menores que podem ser executados em paralelo.
Cada instrução pode ser dividida em uma sequência de etapas, cada uma das quais executada por uma parte separada do processador. O pipelining de instruções permite que várias instruções sigam essas etapas uma após a outra sem ter que esperar que cada instrução termine completamente. O pipelining permite taxas de clock mais altas: com uma etapa de cada instrução concluída em cada ciclo de clock, seria necessário menos tempo para cada ciclo do que se instruções completas tivessem que ser concluídas uma de cada vez.
O pipeline RISC clássico contém cinco estágios: busca de instruções, decodificação de instruções, execução de instruções, acesso à memória e write-back. Os processadores modernos dividem a execução em muito mais etapas, produzindo um pipeline mais profundo com mais estágios (e aumentando as taxas de clock atingíveis, pois cada estágio é menor e leva menos tempo para ser concluído), mas esse modelo deve fornecer um entendimento básico de como o pipelining funciona.
Fonte da imagem
No entanto, a tubulação pode apresentar riscos que devem ser resolvidos para garantir a execução correta do programa.
Como partes diferentes de cada instrução estão sendo executadas ao mesmo tempo, é possível que ocorram conflitos que interfiram na execução correta. Estes são chamados de perigos . Existem três tipos de riscos: dados, estruturais e controle.
Os riscos de dados ocorrem quando as instruções lêem e modificam os mesmos dados ao mesmo tempo ou na ordem errada, levando a resultados incorretos. Riscos estruturais ocorrem quando várias instruções precisam usar uma parte específica do processador ao mesmo tempo. Os riscos de controle ocorrem quando uma instrução de ramificação condicional é encontrada.
Esses riscos podem ser resolvidos de várias maneiras. A solução mais simples é simplesmente parar o pipeline, colocando temporariamente a execução de uma ou instruções no pipeline em espera para garantir resultados corretos. Isso é evitado sempre que possível, pois reduz o desempenho. Para riscos de dados, técnicas como encaminhamento de operando são usadas para reduzir as paradas. Os riscos de controle são tratados através da previsão de ramificação , que requer tratamento especial e é abordada na próxima seção.
A previsão de ramificação é usada para resolver os riscos de controle que podem atrapalhar todo o pipeline.
Os riscos de controle, que ocorrem quando um ramo condicional é encontrado, são particularmente graves. As ramificações introduzem a possibilidade de que a execução continue em outro lugar do programa, em vez de simplesmente na próxima instrução no fluxo de instruções, com base em uma condição específica ser verdadeira ou falsa.
Como a próxima instrução a ser executada não pode ser determinada até que a condição da ramificação seja avaliada, não é possível inserir nenhuma instrução no pipeline após uma ramificação na ausência. Portanto, o oleoduto é esvaziado ( descarregado ), o que pode desperdiçar quase o número de ciclos de relógio que há estágios no oleoduto. As ramificações tendem a ocorrer com muita frequência nos programas; portanto, os riscos de controle podem afetar seriamente o desempenho do processador.
A previsão de ramificação soluciona esse problema, adivinhando se uma ramificação será tomada. A maneira mais simples de fazer isso é simplesmente assumir que os galhos são sempre obtidos ou nunca. No entanto, os processadores modernos usam técnicas muito mais sofisticadas para maior precisão de previsão. Em essência, o processador controla as ramificações anteriores e usa essas informações de várias maneiras para prever a próxima instrução a ser executada. O pipeline pode então ser alimentado com instruções do local correto, com base na previsão.
Obviamente, se a previsão estiver errada, quaisquer instruções foram colocadas no pipeline depois que a ramificação deve ser descartada, liberando o pipeline. Como resultado, a precisão do preditor de ramificação se torna cada vez mais crítica à medida que os pipelines ficam cada vez mais longos. Técnicas específicas de predição de ramificações estão além do escopo desta resposta.
Os caches são usados para acelerar os acessos à memória.
Processadores modernos podem executar instruções e processar dados muito mais rapidamente do que podem ser acessados na memória principal. Quando o processador deve acessar a RAM, a execução pode parar por longos períodos de tempo até que os dados estejam disponíveis. Para atenuar esse efeito, pequenas áreas de memória de alta velocidade chamadas caches são incluídas no processador.
Devido ao espaço limitado disponível na matriz do processador, os caches são de tamanho muito limitado. Para aproveitar ao máximo essa capacidade limitada, os caches armazenam apenas os dados acessados mais recentemente ou com mais frequência ( localidade temporal ). Como os acessos à memória tendem a ser agrupados em áreas específicas ( localidade espacial ), os blocos de dados próximos ao que é acessado recentemente também são armazenados no cache. Consulte: Localidade de referência
Os caches também são organizados em vários níveis de tamanho variável para otimizar o desempenho, pois caches maiores tendem a ser mais lentos que caches menores. Por exemplo, um processador pode ter um cache de nível 1 (L1) com apenas 32 KB de tamanho, enquanto o cache de nível 3 (L3) pode ter vários megabytes de tamanho. O tamanho do cache, bem como a associatividade do cache, que afeta a maneira como o processador gerencia a substituição de dados em um cache completo, afeta significativamente os ganhos de desempenho obtidos através de um cache.
A execução fora de ordem reduz as paradas devido a riscos, permitindo que instruções independentes sejam executadas primeiro.
Nem todas as instruções em um fluxo de instruções dependem uma da outra. Por exemplo, embora a + b = c
deve ser executado antes c + d = e
, a + b = c
e d + e = f
são independentes e podem ser executadas ao mesmo tempo.
A execução fora de ordem aproveita esse fato para permitir que outras instruções independentes sejam executadas enquanto uma instrução está paralisada. Em vez de exigir instruções para executar uma após a outra na etapa de bloqueio, ohardware de agendamento é adicionado para permitir que instruções independentes sejam executadas em qualquer ordem. As instruções são enviadas para uma fila de instruções e emitidas para a parte apropriada do processador quando os dados necessários estiverem disponíveis. Dessa forma, as instruções bloqueadas aguardando dados de uma instrução anterior não vinculam instruções posteriores independentes.
Fonte da imagem
- Várias estruturas de dados novas e expandidas são necessárias para executar a execução fora de ordem. A fila de instruções acima mencionada, a estação de reserva , é usada para conter instruções até que os dados necessários para a execução estejam disponíveis. O buffer de reordenação (ROB) é usado para acompanhar o estado das instruções em andamento, na ordem em que foram recebidas, para que as instruções sejam concluídas na ordem correta. Um arquivo de registro que se estende além do número de registros fornecidos pela própria arquitetura é necessário para renomear o registro , o que ajuda a impedir que instruções independentes se tornem dependentes devido à necessidade de compartilhar o conjunto limitado de registros fornecidos pela arquitetura.
Arquiteturas superescalares permitem que várias instruções em um fluxo de instruções sejam executadas ao mesmo tempo.
As técnicas discutidas acima apenas aumentam o desempenho do pipeline de instruções. Somente essas técnicas não permitem que mais de uma instrução seja concluída por ciclo de clock. No entanto, muitas vezes é possível executar instruções individuais em um fluxo de instruções em paralelo, como quando elas não dependem uma da outra (conforme discutido na seção de execução fora de ordem acima).
As arquiteturas superescalares aproveitam esse paralelismo no nível das instruções, permitindo que as instruções sejam enviadas para várias unidades funcionais ao mesmo tempo. O processador pode ter várias unidades funcionais de um tipo específico (como ALUs inteiras) e / ou diferentes tipos de unidades funcionais (como unidades de ponto flutuante e unidades inteiras) para as quais as instruções podem ser enviadas simultaneamente.
Em um processador superescalar, as instruções são agendadas como em um projeto fora de ordem, mas agora existem várias portas de emissão , permitindo que diferentes instruções sejam emitidas e executadas ao mesmo tempo. O circuito expandido de decodificação de instruções permite que o processador leia várias instruções de cada vez em cada ciclo de clock e determine as relações entre elas. Um processador moderno de alto desempenho pode agendar até oito instruções por ciclo de clock, dependendo do que cada instrução faz. É assim que os processadores podem concluir várias instruções por ciclo de clock. Veja: mecanismo de execução Haswell no AnandTech
Fonte da imagem
- No entanto, arquiteturas superescalares são muito difíceis de projetar e otimizar. A verificação de dependências entre instruções requer uma lógica muito complexa, cujo tamanho pode ser escalonado exponencialmente à medida que o número de instruções simultâneas aumenta. Além disso, dependendo do aplicativo, há apenas um número limitado de instruções em cada fluxo de instruções que podem ser executadas ao mesmo tempo; portanto, os esforços para tirar maior proveito do ILP sofrem com retornos decrescentes.
São adicionadas instruções mais avançadas que executam operações complexas em menos tempo.
À medida que os orçamentos dos transistores aumentam, torna-se possível implementar instruções mais avançadas que permitem que operações complexas sejam executadas em uma fração do tempo que elas levariam. Os exemplos incluem conjuntos de instruções vetoriais , como SSE e AVX, que executam cálculos em várias partes de dados ao mesmo tempo e o conjunto de instruções AES, que acelera a criptografia e descriptografia de dados.
Para executar essas operações complexas, os processadores modernos usam microoperações (μops) . Instruções complexas são decodificadas em seqüências de µops, que são armazenadas dentro de um buffer dedicado e agendadas para execução individualmente (na extensão permitida pelas dependências de dados). Isso fornece mais espaço para o processador explorar o ILP. Para aprimorar ainda mais o desempenho, um cache de µop especial pode ser usado para armazenar µops recentemente decodificados, para que os µops das instruções recentemente executadas possam ser consultados rapidamente.
No entanto, a adição dessas instruções não aumenta automaticamente o desempenho. Novas instruções podem aumentar o desempenho apenas se um aplicativo for escrito para usá-las. A adoção dessas instruções é dificultada pelo fato de os aplicativos que as utilizarem não funcionarem em processadores mais antigos que não os suportam.
Então, como essas técnicas melhoram o desempenho do processador ao longo do tempo?
Os oleodutos tornaram-se mais longos ao longo dos anos, reduzindo a quantidade de tempo necessária para concluir cada estágio e, portanto, permitindo maiores taxas de clock. No entanto, entre outras coisas, pipelines mais longos aumentam a penalidade por uma previsão de ramificação incorreta, portanto, um pipeline não pode ser muito longo. Ao tentar atingir velocidades de clock muito altas, o processador Pentium 4 usou pipelines muito longos, até 31 estágios em Prescott . Para reduzir os déficits de desempenho, o processador tentaria executar instruções mesmo que falhassem e continuaria tentando até obter êxito . Isso levou a um consumo de energia muito alto e reduziu o desempenho obtido com o hyperthreading . Os processadores mais novos não usam mais pipelines por tanto tempo, especialmente porque o escalonamento da taxa de clock atingiu uma parede;Haswell usa um pipeline que varia entre 14 e 19 estágios e as arquiteturas de menor consumo de energia usam pipelines mais curtos (o Intel Atom Silvermont tem de 12 a 14 estágios).
A precisão da previsão de ramificação melhorou com arquiteturas mais avançadas, reduzindo a frequência de descargas de pipeline causadas por erros de previsão e permitindo que mais instruções sejam executadas simultaneamente. Considerando o comprimento dos pipelines nos processadores atuais, isso é fundamental para manter o alto desempenho.
Com o aumento dos orçamentos dos transistores, caches maiores e mais eficazes podem ser incorporados no processador, reduzindo as paradas devido ao acesso à memória. O acesso à memória pode exigir mais de 200 ciclos para ser concluído nos sistemas modernos, por isso é importante reduzir a necessidade de acessar a memória principal o máximo possível.
Os processadores mais novos podem aproveitar melhor o ILP por meio de lógica de execução superescalar mais avançada e designs "mais amplos" que permitem que mais instruções sejam decodificadas e executadas simultaneamente. A arquitetura Haswell pode decodificar quatro instruções e despachar 8 micro-operações por ciclo de clock. Os orçamentos crescentes de transistor permitem que unidades mais funcionais, como ALUs inteiros, sejam incluídas no núcleo do processador. As estruturas de dados principais usadas na execução fora de ordem e superescalar, como a estação de reserva, o buffer de pedido e o arquivo de registro, são expandidas em designs mais recentes, o que permite ao processador procurar uma janela mais ampla de instruções para explorar seu ILP. Essa é uma importante força motriz por trás dos aumentos de desempenho nos processadores atuais.
Instruções mais complexas estão incluídas nos processadores mais recentes, e um número crescente de aplicativos usa essas instruções para aprimorar o desempenho. Os avanços na tecnologia do compilador, incluindo melhorias na seleção de instruções e na vetorização automática , permitem o uso mais eficaz dessas instruções.
Além do exposto, uma maior integração de peças anteriormente externas à CPU, como a ponte norte, o controlador de memória e as faixas PCIe, reduz a E / S e a latência da memória. Isso aumenta a taxa de transferência, reduzindo as paradas causadas por atrasos no acesso a dados de outros dispositivos.