Supondo que as solicitações de cache l1 e l2 resultem em uma falha, o processador trava até que a memória principal seja acessada?
Ouvi falar da ideia de mudar para outro thread; se sim, o que é usado para ativar o thread parado?
Supondo que as solicitações de cache l1 e l2 resultem em uma falha, o processador trava até que a memória principal seja acessada?
Ouvi falar da ideia de mudar para outro thread; se sim, o que é usado para ativar o thread parado?
Respostas:
A latência da memória é um dos problemas fundamentais estudados na pesquisa em arquitetura de computadores.
A execução especulativa com problema de instrução fora de ordem geralmente é capaz de encontrar trabalho útil para preencher a latência durante uma ocorrência de cache L1, mas geralmente fica sem trabalho útil após 10 ou 20 ciclos ou mais. Houve várias tentativas de aumentar a quantidade de trabalho que pode ser realizado durante uma falta de longa latência. Uma idéia era tentar fazer predição de valor (Lipasti, Wilkerson e Shen, (ASPLOS-VII): 138-147, 1996). Essa idéia estava muito na moda nos círculos de pesquisa da arquitetura acadêmica por um tempo, mas parece não funcionar na prática. Uma última tentativa de salvar a previsão de valor do caixote do lixo da história foi a execução de runahead(Mutlu, Stark, Wilkerson e Patt (HPCA-9): 129, 2003). Na execução com runahead, você reconhece que suas previsões de valor estarão erradas, mas especulativamente executa de qualquer maneira e depois lança todo o trabalho com base na previsão, na teoria de que você pelo menos iniciará alguns prefets para o que seria o cache L2 sente falta. Acontece que o runahead desperdiça tanta energia que simplesmente não vale a pena.
Uma abordagem final nesse sentido, que pode estar recebendo alguma tração na indústria, envolve a criação de buffers de reabastecimento enormemente longos. As instruções são executadas especulativamente com base na previsão de ramificação, mas nenhuma previsão de valor é feita. Em vez disso, todas as instruções que dependem de uma falha de carregamento de longa latência ficam sentadas e aguardam no buffer de reordenação. Mas como o buffer de reordenação é tão grande, você pode continuar buscando instruções se o preditor de ramificação estiver fazendo um trabalho decente, às vezes você poderá encontrar um trabalho útil muito mais tarde no fluxo de instruções. Um trabalho de pesquisa influente nessa área foram os dutos de fluxo contínuo(Srinivasan, Rajwar, Akkary, Gandhi e Upton (ASPLOS-XI): 107-119, 2004). (Apesar do fato de todos os autores serem da Intel, acredito que a ideia tenha mais força na AMD.)
O uso de vários encadeamentos para tolerância à latência tem um histórico muito mais longo, com muito maior sucesso no setor. Todas as versões bem-sucedidas usam suporte de hardware para multithreading. A versão mais simples (e mais bem-sucedida) disso é o que costuma ser chamado de FGMT ( multi-threading de granulação fina ) ou multi-threading intercalado . Cada núcleo de hardware suporta vários contextos de encadeamento (um contexto é essencialmente o estado de registro, incluindo registros como o ponteiro de instruções e qualquer sinalizador implícito). Em um processador multithread de granulação fina, cada thread é processado em-ordem. O processador controla quais encadeamentos estão paralisados em uma falta de carregamento de longa latência e quais estão prontos para a próxima instrução e usa uma estratégia simples de planejamento FIFO em cada ciclo para escolher qual encadeamento pronto para executar esse ciclo. Um exemplo inicial disso em larga escala foram os processadores HEP de Burton Smith (Burton Smith passou a arquitetar o supercomputador Tera, que também era um processador multithread de granulação fina). Mas a ideia remonta muito mais aos anos 1960, eu acho.
O FGMT é particularmente eficaz no fluxo de cargas de trabalho. Todas as GPUs modernas (unidades de processamento gráfico) são multicore, onde cada núcleo é FGMT, e o conceito também é amplamente usado em outros domínios de computação. O T1 da Sun também era FMGT multicore, assim como o Xeon Phi da Intel (o processador que ainda é chamado de "MIC" e costumava ser chamado de "Larabee").
A idéia de multithreading simultâneo (Tullsen, Eggers e Levy, (ISCA-22): 392-403, 1995) combina multithreading de hardware com execução especulativa. O processador possui vários contextos de encadeamento, mas cada encadeamento é executado especulativamente e fora de ordem. Um planejador mais sofisticado pode usar várias heurísticas para buscar no encadeamento que provavelmente terá um trabalho útil ( Malik, Agarwal, Dhar e Frank, (HPCA-14: 50-61), 2008 ). Uma certa grande empresa de semicondutores começou a usar o termo hyperthreading para multithreading simultâneo, e esse nome parece ser o mais usado atualmente.
Depois de reler seus comentários, percebi que você também está interessado na sinalização que ocorre entre processador e memória. Os caches modernos geralmente permitem que várias falhas sejam simultaneamente destacadas. Isso é chamado de cache sem bloqueio (Kroft, (ISCA-8): 81-87, 1981). (Mas o documento é difícil de encontrar on-line e um tanto difícil de ler. Resposta curta: há muita contabilidade, mas você lida com isso. A estrutura de contabilidade de hardware é chamada MSHR (falta de informações / registro de status) ), que é o nome que Kroft deu em seu trabalho de 1981).
A resposta curta é: nada, o processador trava.
Não há tantas possibilidades. Mudar para uma tarefa diferente não é realmente uma opção por dois motivos. Essa é uma operação cara e, como a tarefa atual e outras tarefas estão competindo por espaço no cache, a mudança para a outra tarefa pode exigir um acesso à memória principal e, assim, retornar à tarefa original. Além disso, isso teria que envolver o sistema operacional, portanto o processador teria que acionar alguma forma de interrupção ou interceptação - na verdade, o processador estaria mudando para algum código do kernel.
Enquanto o processador está parado, o timer continua a funcionar, portanto, pode haver uma interrupção no timer ou nos outros periféricos. Portanto, é mais provável que uma troca de contexto ocorra durante um acesso à memória principal do que durante um acesso ao cache, mas apenas porque leva mais tempo.
No entanto, os computadores modernos incluem uma variedade de técnicas para tentar reduzir o tempo perdido no processador aguardando a memória principal. Parar acontece, mas apenas quando não podia ser evitado.
Uma técnica é a busca especulativa : o processador tenta adivinhar qual local da memória será acessado e o busca em cache antes do tempo. Por exemplo, loops sobre um bloco de memória são comuns; portanto, se as linhas de cache foram carregadas para os endereços de memória 0x12340000, 0x12340010 e 0x12340020, pode ser uma boa ideia carregar a linha para 0x12340030. O compilador pode ajudar gerando instruções de pré-busca que são como cargas, exceto que eles transferem apenas dados da memória principal para o cache, e não para um registro do processador.
Outra técnica é a execução especulativa . O processador começa a executar a próxima instrução antes que a carga seja executada. Isso acontece naturalmente de qualquer maneira devido ao pipeline de instruções. Somente instruções que não dependem do valor carregado podem ser executadas desta maneira: o processador deve executar uma análise de dependência. Para instruções condicionais (por exemplo, carregar r1; ramificar se r1 ≠ 0), os processadores empregam heurísticas de previsão de ramificação para adivinhar qual será o valor. A execução especulativa após uma carga pode precisar ser rebobinada, caso a carga provoque um cancelamento.
Algumas arquiteturas como o Itanium facilitam a execução de instruções em uma ordem conveniente, permitindo o reordenamento de instruções por padrão: em vez de consistir em uma sequência de instruções elementares que são semanticamente executadas uma após a outra, os programas consistem em palavras muito longas : uma única instrução inclui muitas operações que devem ser executadas em paralelo por diferentes componentes do processador.
A mudança para outro encadeamento acontece no hyperthreading , encontrado nos processadores x86 de ponta. Essa é uma técnica de design de hardware: cada núcleo do processador contém dois bancos de registros separados (cada um correspondente a um contexto de tarefa), mas uma instância única de outros elementos, para que ele possa suportar dois encadeamentos de execução independentes, mas executar efetivamente apenas instruções de um em um tempo. Enquanto um segmento está parado, o outro segmento continua. Do ponto de vista do software, existem dois processadores independentes; acontece que esses processadores compartilham muitos componentes sob o capô.
A troca é mais um nível na hierarquia do cache de memória: a memória principal pode ser vista como um cache para o espaço de troca. Com a troca, os mecanismos e as taxas de desempenho são diferentes. Se uma tarefa precisar que os dados sejam carregados do swap, a instrução load acionará uma interceptação que executa o código do kernel para alocar uma página na RAM e carregar seu conteúdo do disco. Enquanto isso acontece, o kernel pode decidir mudar para outra tarefa.
A resposta a esta pergunta varia de acordo com a arquitetura em questão. Embora muitas CPUs parem (ARM, x86 sem hyperthreading etc.) porque leva muito tempo para alternar threads, essa não é a abordagem adotada por todas as arquiteturas. Em algumas arquiteturas, cada encadeamento agendado em uma CPU possui seu próprio arquivo de registro independente; portanto, o processador pode simplesmente executar o trabalho a partir de um encadeamento que não está aguardando um acesso à memória. Entendo que isso é, até certo ponto, o que o hyperthreading x86 faz (usando apenas 2 threads), mas é muito mais comum no GPGPUarquiteturas. No caso específico da CUDA, pelo menos dezenas, se não centenas, de warps de threads geralmente são carregados em um determinado multiprocessador a qualquer momento, com cada thread (centenas ou milhares deles) tendo seus próprios registros. Isso permite que a arquitetura execute uma instrução de outro encadeamento no ciclo seguinte, quando um determinado encadeamento emite um acesso à memória. Portanto, contanto que muitos threads sejam carregados, os núcleos do processador nunca ficam inativos para acessar a memória. Consulte as Diretrizes de desempenho e a hierarquia de memória para obter mais informações.