O uso de vários núcleos requer a exposição explícita do paralelismo no nível do encadeamento ao sistema operacional, o que geralmente exige que o programador grave um programa com vários encadeamentos . (Ou para executar um programa de thread único várias vezes em entradas diferentes, como compilar com make -j4
)
Os compiladores para alguns idiomas oferecem suporte à paralelização automática. Por exemplo, C ou C ++ com OpenMP podem compilar um for()
loop comum em um programa que inicia vários threads.
#pragma omp parallel for
for(int i = 0; i < 1000000; ++i)
{
A[i] = B[i] * constant + C[i];
}
Mas ainda assim, isso precisa acontecer quando você escreve ou compila o programa. Não há como o hardware e os sistemas operacionais atuais usarem vários núcleos para acelerar um programa de thread único.
Relacionado: Como um único encadeamento é executado em vários núcleos? : resposta: eles não. Mas existem outros tipos de paralelismo, como o paralelismo no nível da instrução que um único núcleo da CPU encontra e explora para executar um único encadeamento mais rapidamente do que uma instrução por vez.
Minha resposta a essa pergunta entra em alguns detalhes de como as CPUs modernas encontram e exploram um paralelismo detalhado no nível das instruções. (Principalmente o x86). Isso é apenas parte de como as CPUs normais funcionam, tendo várias instruções em andamento ao mesmo tempo, e não é algo que você precisa habilitar especialmente. (Existem contadores de desempenho que permitem ver quantas instruções por clock sua CPU conseguiu executar durante a execução de um programa ou outras medidas.)
Observe que o RPi3 usa núcleos de CPU ARM Cortex-A53 em ordem . Cada núcleo é superescalar de 2 largos (2 instruções por relógio, conforme o ILP permite), mas não pode reordenar as instruções para encontrar mais paralelismo no nível das instruções e ocultar a latência.
Ainda assim, a CPU é canalizada, portanto, o número total de instruções em andamento (desde a busca e decodificação até o estágio de write-back no final do pipeline) é significativo. Quando as dependências de dados não limitam as coisas, pode haver 2 instruções em cada estágio do pipeline em que a CPU está trabalhando, com uma taxa de transferência de 2 instruções por relógio. (Isso é o que significa 2).
Ele não pode executar instruções fora de ordem, mas com uma ordenação cuidadosa de instruções (geralmente por um compilador), ainda pode ocultar a latência de uma instrução que leva vários ciclos para que sua saída esteja pronta. (por exemplo, uma carga, mesmo que seja atingida no cache ou em uma multiplicação, levará vários ciclos, contra uma adição estar pronta no próximo ciclo). O truque é solicitar as instruções asm para que haja várias instruções independentes entre a que produz um resultado e a que o utiliza.
Ter o software (um compilador) agendando estaticamente as instruções é mais frágil do que o hardware que pode ser reordenado internamente, preservando a ilusão de execução na ordem do programa. É muito difícil para os compiladores fazerem um trabalho tão bom quanto uma pequena janela fora de ordem para reordenar as instruções, porque as falhas no cache são imprevisíveis e é difícil analisar as cadeias de dependência nas chamadas de função em tempo de compilação. E o número de registros é limitado sem a renomeação de registros de hardware.
Tudo isso é um pequeno conforto quando o código é mais lento do que você gostaria. Certamente, há um monte de coisas legais embaixo do capô em um Cortex-A53, mas há mais coisas legais embaixo do capô em um Cortex-A57 (como execução fora de ordem de até 3 instruções por relógio) e ainda mais uma grande CPU x86 como a Skylake (sem mencionar as diferenças de velocidade do relógio).
O Cortex-A53 é fantástico, comparado a um https://en.wikipedia.org/wiki/Classic_RISC_pipeline como MIPS original que você aprenderia na aula de arquitetura de computadores, mas, pelos padrões modernos, é bastante barato.