GNU make: o número de trabalhos deve ser igual ao número de núcleos da CPU em um sistema?

87

Parece haver alguma controvérsia sobre se o número de trabalhos no GNU make é suposto ser igual ao número de núcleos, ou se você pode otimizar o tempo de construção adicionando um trabalho extra que pode ser colocado na fila enquanto os outros "trabalham" .

É melhor usar -j4ou -j5em um sistema quad core?

Você viu (ou fez) algum benchmarking que apóia um ou outro?

João
fonte
8
Apenas para a dica, você pode usar make `nproc`para fazer um script independente da CPU :)
VivienG
Se você tiver uma mistura de receitas vinculadas a io e cpu, provavelmente vai querer muito mais do que NCPUs. Considere também adicionar opções -lX. Esta não é realmente uma pergunta respondível, a não ser "depende do seu hardware e das tarefas feitas".
James Moore
É tecnicamente possível ver uma melhoria. Você precisa de um disco lento, sem memória RAM suficiente e muitos pequenos arquivos de código-fonte. Mais fácil de vir uma década atrás.
Hans Passant

Respostas:

56

Eu diria que a melhor coisa a fazer é comparar você mesmo com seu ambiente e carga de trabalho específicos. Parece que existem muitas variáveis ​​(tamanho / número de arquivos de origem, memória disponível, cache de disco, se o diretório de origem e os cabeçalhos do sistema estão localizados em discos diferentes, etc.) para uma resposta única.

Minha experiência pessoal (em um MacBook Pro de 2 núcleos) é que -j2 é significativamente mais rápido que -j1, mas além disso (-j3, -j4 etc.) não há aumento de velocidade mensurável. Portanto, para o meu ambiente, "jobs == number of core" parece ser uma boa resposta. (YMMV)

David Gelhar
fonte
57

Executei meu projeto doméstico no meu laptop de 4 núcleos com hyperthreading e registrei os resultados. Este é um projeto bastante pesado do compilador, mas inclui um teste de unidade de 17,7 segundos no final. As compilações não são muito intensivas em IO; há muita memória disponível e se não, o resto está em um SSD rápido.

1 job        real   2m27.929s    user   2m11.352s    sys    0m11.964s    
2 jobs       real   1m22.901s    user   2m13.800s    sys    0m9.532s
3 jobs       real   1m6.434s     user   2m29.024s    sys    0m10.532s
4 jobs       real   0m59.847s    user   2m50.336s    sys    0m12.656s
5 jobs       real   0m58.657s    user   3m24.384s    sys    0m14.112s
6 jobs       real   0m57.100s    user   3m51.776s    sys    0m16.128s
7 jobs       real   0m56.304s    user   4m15.500s    sys    0m16.992s
8 jobs       real   0m53.513s    user   4m38.456s    sys    0m17.724s
9 jobs       real   0m53.371s    user   4m37.344s    sys    0m17.676s
10 jobs      real   0m53.350s    user   4m37.384s    sys    0m17.752s
11 jobs      real   0m53.834s    user   4m43.644s    sys    0m18.568s
12 jobs      real   0m52.187s    user   4m32.400s    sys    0m17.476s
13 jobs      real   0m53.834s    user   4m40.900s    sys    0m17.660s
14 jobs      real   0m53.901s    user   4m37.076s    sys    0m17.408s
15 jobs      real   0m55.975s    user   4m43.588s    sys    0m18.504s
16 jobs      real   0m53.764s    user   4m40.856s    sys    0m18.244s
inf jobs     real   0m51.812s    user   4m21.200s    sys    0m16.812s

Resultados básicos:

  • O dimensionamento para a contagem de núcleos aumenta o desempenho quase linearmente. O tempo real caiu de 2,5 minutos para 1,0 minuto (2,5x mais rápido), mas o tempo gasto durante a compilação aumentou de 2,11 para 2,50 minutos. O sistema notou quase nenhuma carga adicional neste bit.
  • O escalonamento da contagem de núcleo para a contagem de encadeamentos aumentou imensamente a carga do usuário, de 2,50 minutos para 4,38 minutos. Essa quase duplicação é mais provável porque as outras instâncias do compilador queriam usar os mesmos recursos da CPU ao mesmo tempo. O sistema está ficando um pouco mais carregado com solicitações e troca de tarefas, levando a 17,7 segundos de tempo gasto. A vantagem é de cerca de 6,5 segundos em um tempo de compilação de 53,5 segundos, resultando em um aumento de velocidade de 12%.
  • A escala da contagem de encadeamentos para a contagem dupla de encadeamentos não proporcionou aumento significativo. Os tempos em 12 e 15 são anomalias estatísticas mais prováveis ​​que você pode ignorar. O tempo total gasto aumenta ligeiramente, assim como o tempo do sistema. Ambos são provavelmente devido à maior troca de tarefas. Não há benefício nisso.

Meu palpite agora: se você fizer outra coisa no seu computador, use a contagem de núcleos. Caso contrário, use a contagem de threads. Exceder isso não mostra nenhum benefício. Em algum ponto, eles ficarão com a memória limitada e entrarão em colapso devido a isso, tornando a compilação muito mais lenta. A linha "inf" foi adicionada em uma data muito posterior, dando-me a suspeita de que havia algum afogamento térmico para os trabalhos 8+. Isso mostra que para esse tamanho de projeto não há memória ou limite de rendimento em vigor. É um projeto pequeno, porém, com 8 GB de memória para compilar.

elegante
fonte
De acordo com stackoverflow.com/questions/56272639/… , você pode obter uma vantagem executando mais tarefas do que CPUs, mas apenas se suas tarefas gastarem uma parte significativa do tempo esperando por E / S de rede. Para tarefas de compilação, este não é o caso.
ivan_pozdeev
30

Eu, pessoalmente, uso make -j nonde n é "número de núcleos" + 1.

Não posso, no entanto, dar uma explicação científica: tenho visto muitas pessoas usando as mesmas configurações e me deram resultados muito bons até agora.

De qualquer forma, você tem que ter cuidado porque alguns make-chains simplesmente não são compatíveis com a --jobsopção e podem levar a resultados inesperados. Se você estiver enfrentando erros de dependência estranhos, tente makesem --jobs.

ereOn
fonte
19
A explicação (embora não possa atestar sua cientificidade) é que "+ 1" dá um trabalho extra que é executado enquanto qualquer um dos outros n trabalhos está fazendo E / S.
Laurynas Biveinis
@LaurynasBiveinis: Mas, então, os trabalhos estão sendo executados em núcleos diferentes o tempo todo, pelo menos com mais frequência do que em uma configuração mais conservadora, onde um trabalho tem a chance de permanecer no mesmo núcleo por um período mais longo. Existem prós e contras aqui ...
krlmlr
1
Número de núcleos + 1 também é minha configuração padrão. Um problema é que, em qualquer sistema razoavelmente grande, o make parece atrasar a vinculação e executa todas as etapas de vinculação juntas. Neste ponto, você fica sem RAM. Bah!
bobbogo
4
alguns make-chains simplesmente não são compatíveis com a opção --jobs -> Isso significa que você tem dependências ausentes. Corrija seus makefiles se você conseguir isso.
dascandy
7

No final das contas, você terá que fazer alguns benchmarks para determinar o melhor número a ser usado para sua construção, mas lembre-se de que a CPU não é o único recurso que importa!

Se você tem uma construção que depende muito do disco, por exemplo, a geração de muitos trabalhos em um sistema multicore pode ser mais lenta , pois o disco terá que fazer um trabalho extra movendo a cabeça do disco para frente e para trás para atender a todos os diferentes trabalhos (dependendo de muitos fatores, como o quão bem o sistema operacional lida com o cache de disco, suporte de enfileiramento de comando nativo pelo disco, etc.).

E então você tem núcleos "reais" versus hyper-threading. Você pode ou não se beneficiar da geração de jobs para cada hyper-thread. Novamente, você terá que fazer um benchmark para descobrir.

Não posso dizer que tentei especificamente #cores + 1 , mas em nossos sistemas (Intel i7 940, 4 núcleos hyperthreaded, muita RAM e drives VelociRaptor) e nosso build (build C ++ em grande escala que alterna CPU e I / O) há muito pouca diferença entre -j4 e -j8. (É talvez 15% melhor ... mas nem de longe duas vezes melhor.)

Se vou almoçar fora, usarei -j8, mas se quiser usar meu sistema para qualquer outra coisa durante a construção, usarei um número menor. :)

ijprest
fonte
1
Parece ótimo, mas estou confuso por que você não levaria + 15% todas as vezes usando-j 8
sg
1
@sg: j8 foi realmente desgastante para o sistema que descrevi no meu post original ... a máquina ainda era utilizável , mas definitivamente respondia menos. Portanto, se eu ainda quisesse usá-lo interativamente para outras tarefas (normalmente trabalhando em outro código e talvez na construção de uma única DLL ocasional), reservaria alguns núcleos para os bits interativos.
ijprest
@sg: Este é um problema menor em nossos sistemas mais novos ... Suspeito que seja principalmente porque estamos executando SSDs agora. (Acho que estamos totalmente ligados à CPU agora que vamos para SSDs ... tentamos construir inteiramente em um drive de RAM sem quase nenhuma melhoria.) Mas ainda deixarei alguns núcleos livres se estiver fazendo qualquer coisa mais do que simples edição de texto em primeiro plano.
ijprest
5

Acabei de comprar um proc Athlon II X2 Regor com um Foxconn M / B e 4 GB de memória G-Skill.

Coloquei meu 'cat / proc / cpuinfo' e 'free' no final deste para que outras pessoas possam ver minhas especificações. É um Athlon II x2 dual core com 4 GB de RAM.

uname -a on default slackware 14.0 kernel is 3.2.45.

Eu baixei a próxima etapa do código-fonte do kernel (linux-3.2.46) para / archive4;

extraiu ( tar -xjvf linux-3.2.46.tar.bz2);

cd'd no diretório ( cd linux-3.2.46);

e copiou a configuração do kernel padrão em ( cp /usr/src/linux/.config .);

usado make oldconfigpara preparar a configuração do kernel 3.2.46;

então executou make com vários encantamentos de -jX.

Testei os tempos de cada execução emitindo make após o comando time, por exemplo, 'time make -j2'. Entre cada execução, 'rm -rf' a árvore linux-3.2.46 e a extrai novamente, copiei o /usr/src/linux/.config padrão para o diretório, executei make oldconfig e então fiz meu teste 'make -jX' novamente .

"make" simples:

real    51m47.510s
user    47m52.228s
sys     3m44.985s
bob@Moses:/archive4/linux-3.2.46$

como acima, mas com make -j2

real    27m3.194s
user    48m5.135s
sys     3m39.431s
bob@Moses:/archive4/linux-3.2.46$

como acima, mas com make -j3

real    27m30.203s
user    48m43.821s
sys     3m42.309s
bob@Moses:/archive4/linux-3.2.46$

como acima, mas com make -j4

real    27m32.023s
user    49m18.328s
sys     3m43.765s
bob@Moses:/archive4/linux-3.2.46$

como acima, mas com make -j8

real    28m28.112s
user    50m34.445s
sys     3m49.877s
bob@Moses:/archive4/linux-3.2.46$

'cat / proc / cpuinfo' produz:

bob@Moses:/archive4$ cat /proc/cpuinfo
processor       : 0
vendor_id       : AuthenticAMD
cpu family      : 16
model           : 6
model name      : AMD Athlon(tm) II X2 270 Processor
stepping        : 3
microcode       : 0x10000c8
cpu MHz         : 3399.957
cache size      : 1024 KB
physical id     : 0
siblings        : 2
core id         : 0
cpu cores       : 2
apicid          : 0
initial apicid  : 0
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 5
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmo
v pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rd
tscp lm 3dnowext 3dnow constant_tsc nonstop_tsc extd_apicid pni monitor cx16 p
opcnt lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowpre
fetch osvw ibs skinit wdt npt lbrv svm_lock nrip_save
bogomips        : 6799.91
clflush size    : 64
cache_alignment : 64
address sizes   : 48 bits physical, 48 bits virtual
power management: ts ttp tm stc 100mhzsteps hwpstate

processor       : 1
vendor_id       : AuthenticAMD
cpu family      : 16
model           : 6
model name      : AMD Athlon(tm) II X2 270 Processor
stepping        : 3
microcode       : 0x10000c8
cpu MHz         : 3399.957
cache size      : 1024 KB
physical id     : 0
siblings        : 2
core id         : 1
cpu cores       : 2
apicid          : 1
initial apicid  : 1
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 5
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmo
v pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rd
tscp lm 3dnowext 3dnow constant_tsc nonstop_tsc extd_apicid pni monitor cx16 p
opcnt lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowpre
fetch osvw ibs skinit wdt npt lbrv svm_lock nrip_save
bogomips        : 6799.94
clflush size    : 64
cache_alignment : 64
address sizes   : 48 bits physical, 48 bits virtual
power management: ts ttp tm stc 100mhzsteps hwpstate

'grátis' rende:

bob@Moses:/archive4$ free
             total       used       free     shared    buffers     cached
Mem:       3991304    3834564     156740          0     519220    2515308
sloMoses
fonte
1
O que simplesmente make -jfaz nesse sistema? O make deve verificar a carga e dimensionar o número de processos com base na carga.
docwhat
1
make -jnão limita o número de empregos. Isso geralmente é desastroso em um projeto de médio ou grande porte, pois mais tarefas são bifurcadas do que pode ser suportado pela RAM. A opção que você precisa restringir por carga é -l [load], em conjunto com-j
Matt G
5

Ambos não estão errados. Para ficar em paz consigo mesmo e com o autor do software que você está compilando (diferentes restrições multi-thread / single-thread se aplicam ao próprio nível de software), sugiro que você use:

make -j`nproc`

Notas: nprocé um comando do linux que retornará o número de núcleos / threads (CPU moderna) disponíveis no sistema. Colocá-lo sob marcas `como acima irá passar o número para o comando make.

Informações adicionais: como alguém mencionou, usar todos os núcleos / threads para compilar software pode literalmente sufocar sua caixa até a morte (não responder) e pode até demorar mais do que usar menos núcleos. Como vi um usuário do Slackware aqui postado, ele tinha CPU dual core, mas ainda fornecia testes até j 8, que parou de ser diferente em j 2 (apenas 2 núcleos de hardware que a CPU pode utilizar). Portanto, para evitar que a caixa não responda, sugiro que você a execute assim:

make -j`nproc --ignore=2`

Isso vai passar a saída nprocpara makee subtrair 2 núcleos de seu resultado.

Lúcifer Digital
fonte
3

Apenas como referência:

Da Spawning Multiple Build Jobsseção em LKD :

onde n é o número de empregos a serem gerados. A prática usual é gerar um ou dois jobs por processador. Por exemplo, em uma máquina de processador duplo, pode-se fazer

$ make j4

Nan Xiao
fonte
link quebrado, esta é uma citação de Linux Kernel Development de Robert Love?
Behrooz
Sim, é desse livro.
Nan Xiao
1

Pela minha experiência, deve haver alguns benefícios de desempenho ao adicionar trabalhos extras. É simplesmente porque a E / S de disco é um dos gargalos além da CPU. No entanto, não é fácil decidir sobre o número de trabalhos extras, pois está altamente interconectado com o número de núcleos e tipos de disco sendo usados.

Matt
fonte
1

Muitos anos depois, a maioria dessas respostas ainda está correta. No entanto, houve uma pequena mudança: usar mais jobs do que núcleos físicos agora oferece uma aceleração genuinamente significativa. Como um adendo à tabela de Dascandy, aqui está meu tempo para compilar um projeto em um AMD Ryzen 5 3600X no Linux. (The Powder Toy, commit c6f653ac3cef03acfbc44e8f29f11e1b301f1ca2)

Eu recomendo verificar você mesmo, mas descobri com a entrada de outras pessoas que usar sua contagem de núcleo lógico para contagem de empregos funciona bem no Zen. Paralelamente, o sistema não parece perder a capacidade de resposta. Imagino que isso também se aplique a CPUs Intel recentes. Observe que eu também tenho um SSD, então pode valer a pena testar sua CPU você mesmo.

scons -j1 --release --native  120.68s user 9.78s system 99% cpu 2:10.60 total
scons -j2 --release --native  122.96s user 9.59s system 197% cpu 1:07.15 total
scons -j3 --release --native  125.62s user 9.75s system 292% cpu 46.291 total
scons -j4 --release --native  128.26s user 10.41s system 385% cpu 35.971 total
scons -j5 --release --native  133.73s user 10.33s system 476% cpu 30.241 total
scons -j6 --release --native  144.10s user 11.24s system 564% cpu 27.510 total
scons -j7 --release --native  153.64s user 11.61s system 653% cpu 25.297 total
scons -j8 --release --native  161.91s user 12.04s system 742% cpu 23.440 total
scons -j9 --release --native  169.09s user 12.38s system 827% cpu 21.923 total
scons -j10 --release --native  176.63s user 12.70s system 910% cpu 20.788 total
scons -j11 --release --native  184.57s user 13.18s system 989% cpu 19.976 total
scons -j12 --release --native  192.13s user 14.33s system 1055% cpu 19.553 total
scons -j13 --release --native  193.27s user 14.01s system 1052% cpu 19.698 total
scons -j14 --release --native  193.62s user 13.85s system 1076% cpu 19.270 total
scons -j15 --release --native  195.20s user 13.53s system 1056% cpu 19.755 total
scons -j16 --release --native  195.11s user 13.81s system 1060% cpu 19.692 total
( -jinf test not included, as it is not supported by scons.)

Testes feitos no Ubuntu 19,10 com Ryzen 5 3600X, Samsung 860 Evo SSD (SATA) e 32 GB de RAM

Nota final: outras pessoas com 3600X podem obter tempos melhores do que eu. Ao fazer este teste, habilitei o modo Eco, reduzindo um pouco a velocidade do processador.

moonheart08
fonte
0

SIM! No meu 3950x, eu executo -j32 e economiza horas de tempo de compilação! Eu ainda posso assistir o youtube, navegar na web, etc. durante a compilação sem qualquer diferença. O processador nem sempre é compatível, mesmo com um nvme de 1 TB 970 PRO ou Auros Gen4 de 1 TB e 64 GB de 3200C14. Mesmo quando é, não percebo a interface do usuário. Planejo testar com -j48 em um futuro próximo em alguns grandes projetos futuros. Espero, como você provavelmente, ver alguma melhora impressionante. Aqueles ainda com um quad-core podem não obter os mesmos ganhos ....

O próprio Linus acabou de atualizar para um 3970x e você pode apostar seu último dólar, ele está executando pelo menos -j64.

preguiçoso
fonte