Considere o seguinte programa de computador muito simples:
for i = 1 to n:
y[i] = x[p[i]]
Aqui e y são n matrizes -element de bytes, e p é um n matriz -element de palavras. Aqui n é grande, por exemplo, n = 2 31 (para que apenas uma fração desprezível dos dados caiba em qualquer tipo de memória cache).
Suponha que consiste em números aleatórios , distribuídos uniformemente entre 1 e n .
Da perspectiva do hardware moderno, isso deve significar o seguinte:
- ler é barato (leitura seqüencial)
- ler é muito caro (leituras aleatórias; quase todas as leituras são falhas de cache; teremos que buscar cada byte individual da memória principal)
- escrever é barato (gravação seqüencial).
E é de fato o que estou observando. O programa é muito lento em comparação com um programa que faz apenas leituras e gravações sequenciais. Ótimo.
Agora vem a pergunta: quão bem esse programa é paralelo às modernas plataformas multinúcleo?
Minha hipótese era que esse programa não se compara bem. Afinal, o gargalo é a memória principal. Um único núcleo já está perdendo a maior parte do tempo apenas aguardando alguns dados da memória principal.
No entanto, não foi isso que observei quando comecei a experimentar alguns algoritmos em que o gargalo era esse tipo de operação!
Simplesmente substituí o loop for ingênuo por um loop for paralelo do OpenMP (em essência, ele apenas dividirá o intervalo em partes menores e executará essas partes em diferentes núcleos da CPU em paralelo).
Em computadores de gama baixa, as acelerações eram de fato menores. Mas em plataformas de ponta, fiquei surpreso por estar recebendo excelentes acelerações quase lineares. Alguns exemplos concretos (os horários exatos podem ser um pouco diferentes, há muitas variações aleatórias; foram apenas experiências rápidas):
2 x Xeon de 4 núcleos (no total 8 núcleos): fator 5-8 acelerações em comparação à versão single-threaded.
Xeon de 2 x 6 núcleos (no total 12 núcleos): acelerações de fator 8 a 14 em comparação com a versão single-threaded.
Agora isso foi totalmente inesperado. Questões:
Precisamente por que esse tipo de programa é tão paralelo ? O que acontece no hardware? (Meu palpite atual é algo assim: as leituras aleatórias de threads diferentes são "canalizadas" e a taxa média de obter respostas a essas perguntas é muito maior do que no caso de um único thread.)
Qual é o modelo teórico correto que poderíamos usar para analisar esse tipo de programa (e fazer previsões corretas do desempenho)?
Edit: Agora há alguns resultados de código-fonte e benchmark disponíveis aqui: https://github.com/suomela/parallel-random-read
- Aproximadamente. 42 ns por iteração (leitura aleatória) com um único encadeamento
- Aproximadamente. 5 ns por iteração (leitura aleatória) com 12 núcleos.