Esta parece ser uma questão básica, mas não consegui encontrá-la em lugar algum. Eu quero obter mais taxa de transferência em um processo pesado de memória, executando muitos deles em uma máquina de muitos núcleos. Esses processos não se comunicam entre si.
Eu esperaria que o tempo para completar para cada processo fosse aproximadamente independente do número de processos em execução até que o número de processos fosse próximo ao número de núcleos físicos (16 no meu caso ).
Eu observo que o tempo-para-completo gradualmente se curva até que seja cerca de 3 vezes mais lento para cada processo ser executado quando 16 estão sendo executados ao mesmo tempo quando apenas um está em execução.
O que está atrasando? (Mais detalhes do que as duas palavras, "alternância de contexto", por favor). Posso fazer algo sobre isso?
Editar: Michael Homer ressalta que estou interessado em um processo pesado de memória, não em um processo pesado de CPU. Suponho que todos esses processadores compartilhem um barramento de memória e esse poderia ser o gargalo. Idealmente, eu gostaria de algum tipo de arquitetura NUMA para colocar a memória do processo "mais próxima" das CPUs. Isso significa que eu preciso procurar hardware diferente para resolver esse problema?
Veja detalhes:
Eu tenho um script simples chamado quickie2.py
que faz algum trabalho aleatório, com uso intensivo da CPU. Eu lanço N deles de uma vez com linhas de comando bash como as seguintes para 14 processos.
for x in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do (python quickie2.py &); done
Aqui estão os tempos até a conclusão de cada N:
N_proc Time to completion (sec)
1 7.29
2 7.28 7.30
3 7.27 7.28 7.38
4 7.01 7.19 7.34 7.43
5 8.41 8.94 9.51 10.27 11.73
6 7.49 7.79 7.97 10.01 10.58 10.85
7 7.71 8.72 10.22 10.43 10.81 10.81 11.42
8 10.1 10.16 10.27 10.29 10.48 10.60 10.66 10.73
9 9.94 11.20 11.27 11.35 11.61 12.43 12.46 12.99 13.53
10 9.26 12.54 12.66 12.84 12.95 13.03 13.06 13.52 13.93 13.95
11 12.46 12.48 12.65 12.74 13.69 13.92 14.14 14.39 14.40 14.69 17.13
12 13.48 13.49 13.51 13.58 13.65 13.67 14.72 14.87 14.89 14.94 15.01 15.06
13 15.47 15.51 16.72 16.79 16.79 16.91 17.00 17.45 17.75 17.78 17.86 18.14 18.48
14 15.14 15.22 16.47 16.53 16.84 17.78 18.07 19.00 19.12 19.32 19.63 19.71 19.80 19.94
15 18.05 18.18 18.58 18.69 19.84 20.70 21.82 21.93 22.13 22.44 22.63 22.81 22.92 23.23 23.23
16 20.96 21.00 21.10 21.21 22.68 22.70 22.76 22.82 24.65 24.66 25.32 25.59 26.16 26.22 26.31 26.38
Editar: A propósito, fixar processos em núcleos torna o processo pior. Veja a linha comentada na listagem de código abaixo.
N_proc Time to completion (sec) with CPU-pinning
1 6.95
2 10.11 10.18
4 19.11 19.11 19.12 19.12
8 20.09 20.12 20.36 20.46 23.86 23.88 23.98 24.16
16 20.24 22.10 22.22 22.24 26.54 26.61 26.64 26.73 26.75 26.78 26.78 26.79 29.41 29.73 29.78 29.90
Aqui está uma captura de tela do htop, mostrando que há exatamente N (14 aqui) núcleos ocupados:
1 [|||||||||||||||98.0%] 5 [|| 5.8%] 9 [||||||||||||||100.0%] 13 [ 0.0%]
2 [||||||||||||||100.0%] 6 [||||||||||||||100.0%] 10 [||||||||||||||100.0%] 14 [||||||||||||||100.0%]
3 [||||||||||||||100.0%] 7 [||||||||||||||100.0%] 11 [||||||||||||||100.0%] 15 [||||||||||||||100.0%]
4 [||||||||||||||100.0%] 8 [||||||||||||||100.0%] 12 [||||||||||||||100.0%] 16 [||||||||||||||100.0%]
Mem[|||||||||||||||||||||||||||||||||||||3952/64420MB] Tasks: 96, 7 thr; 15 running
Swp[ 0/16383MB] Load average: 5.34 3.66 2.29
Uptime: 76 days, 06:59:39
Para completar, aqui está o programa Python que faz algum trabalho. Apenas importa que mantenha a CPU ocupada.
# Code of quickie2.py (for completeness).
import numpy
import time
# import psutil
# psutil.Process().cpu_affinity([int(sys.argv[1])])
arena = numpy.empty(240*1024**2, dtype=numpy.uint8)
startTime = time.time()
# just do some work that takes a lot of CPU
for i in range(100):
one = arena[:80*1024**2].view(numpy.float64)
two = arena[80*1024**2:160*1024**2].view(numpy.float64)
three = arena[160*1024**2:].view(numpy.float64)
three = one + two
print(" {:.2f} ".format(time.time() - startTime))