GNU make:在系统中,作业(jobs)数应该等于CPU核心数吗?

109

关于GNU make中作业数量是否应该等于处理器核心数还是可通过添加一个额外的作业来优化构建时间,这似乎存在一些争议。

在四核系统上,使用-j4还是-j5更好?

您是否看到(或进行)了任何支持其中一种的基准测试?


9
只是提醒一下,你可以使用make \nproc``命令来创建独立于CPU的脚本 :) - VivienG
如果你有一些既涉及io-bound又涉及cpu-bound的任务,那么你可能需要比NCPUs更多的资源。同时考虑添加-lX选项。这个问题并没有一个确切的答案,只能说“取决于你的硬件和任务”。 - James Moore
从技术上讲,看到改进是有可能的。你需要一个慢速磁盘、不足的内存和大量的小源代码文件。这在十年前更容易实现。 - Hans Passant
交叉链接:UNIX&Linux SE上的同一问题 [linux-如何确定传递给make -j选项的最大数量?-Unix&Linux Stack Exchange](https://unix.stackexchange.com/questions/208568/how-to-determine-the-maximum-number-to-pass-to-make-j-option) - user202729
10个回答

65

我已经在我的带超线程的4核笔记本上运行了我的家庭项目,并记录了结果。这个项目编译器相对较重,但最后包括一个17.7秒的单元测试。编译不需要太多IO密集型操作;有很多可用内存,如果没有的话,其余部分都在快速SSD上。

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

基本结果:

  • 将核心数扩展到极限,性能几乎呈线性增长。实时编译时间从2.5分钟缩短到1.0分钟(速度提升2.5倍),但编译期间所需的时间则从2.11增加到了2.50分钟。系统在此过程中几乎没有承受更多的负荷。
  • 将核心数扩展到线程数,则用户负载急剧增加,从2.50分钟增加至4.38分钟。这种近似翻倍的情况很可能是因为其他编译实例同时想要使用相同的 CPU 资源导致的。系统处理请求和任务切换的负担更重了,时间使用率达到了17.7秒。优点是在53.5秒的编译时间中能够节省约6.5秒,实现了12%的加速。
  • 将线程数翻倍也没有显著的加速效果。12和15时的时间差异可能是统计学上的异常值,可以忽略不计。总耗时略微增加,系统时间也有所增加,这很可能是由于任务切换增加所致。这种做法没有任何好处。

我的猜测:如果您的计算机正在进行其他操作,请使用核心数。否则,请使用线程数。超过线程数不会带来任何好处。在某些时候,由于内存限制导致崩溃,编译速度变得更慢。"inf"行是在很晚之后添加的,这让我怀疑在执行8个或更多任务时存在一些热量限制。这表明对于这个项目大小,没有内存或吞吐量限制。但考虑到只有8GB内存可以用于编译,这是一个相对较小的项目。


1
根据https://dev59.com/M7Tma4cB1Zd3GeqP0Q6B#56273407的说法,如果您的任务在等待网络I/O方面花费了相当大的时间,那么运行更多的任务而不是CPU会带来优势。但对于编译任务来说,情况并非如此。 - ivan_pozdeev
我建议使用可用硬件线程总数+1,以允许I/O与CPU使用重叠。如果系统不是专用的(例如,您正在使用带有Web浏览器的桌面计算机),则建议使用较小的数字。 - Mikko Rantalainen
当你说4核心超线程时,你指的是4个物理核心,所以是4c8t吗?如果超线程没有提供任何加速,那么你的“用户”时间增加到1核心总时间的两倍是一致的。如果你还记得,这是什么CPU型号?旧的CPU在某些工作负载下可能会从超线程中受益较少,因为它们不够宽且缓存较小。 - Peter Cordes
谢谢。那是 Nehalem;在 Sandybridge 之前引入了 uop 缓存和其他改进。如何加速 Linux 编译时间 中有一些来自 i7-3930K(Sandbybridge-E)的计时数据。但实际上,你从 -j4-j8 的 10% 加速((59.8-53.5) / 59.8)与 Maxim 从 -j6-j9 的 12% 加速差不多,这被证明是他的 6c12t 机器的最佳选择。 - Peter Cordes
感谢 @PeterCordes - 很高兴看到我十年前测量的数据仍然有效。 - undefined
显示剩余5条评论

61

我认为最好的做法是在你特定的环境和工作负载上自行进行基准测试。看起来有太多的变量(源文件的大小/数量,可用内存,磁盘缓存,源目录和系统头是否位于不同的磁盘上等),无法给出适合所有情况的答案。

根据我的个人经验(在一台2核MacBook Pro上),使用-j2比-j1快得多,但超过这个数量(-j3,-j4等)没有明显的加速效果。因此,在我的环境中,“jobs == number of cores”似乎是一个不错的答案。(但结果可能会有所差异)


32

个人而言,我使用make -j n,其中n为“核心数”+1。

然而,我无法给出科学解释:我看到很多人都使用相同的设置,并且到目前为止它们给了我不错的结果。

无论如何,你必须小心,因为有些make链根本不兼容--jobs选项,可能会导致意外结果。如果你遇到奇怪的依赖错误,请尝试不使用--jobs进行make


21
解释(虽然不能保证其科学性)是“+1”会提供一个额外的工作,当其他n个工作中的任何一个正在进行I/O时,该工作会运行。 - Laurynas Biveinis
@LaurynasBiveinis:但是在这种情况下,作业始终在不同的核心上运行,至少比更保守的设置更频繁,其中作业有机会在同一核心上停留更长的时间。这里存在利弊... - krlmlr
1
我的默认设置也是核心数量 + 1。一个问题是,在任何相当大的系统中,make似乎会延迟链接并将所有链接步骤一起完成。此时你会用完RAM。啊! - bobbogo
6
有些 makefile 与 --jobs 参数不兼容 -> 这意味着你缺少了依赖项。如果出现这种情况,请修复你的 makefile。 - dascandy
使用+1的想法是尝试启动一个进程,该进程将停止以完成I/O,并在等待I/O时运行另一个(已经完成I/O)。如果您的CPU速度比I/O设备快得多,并且您有足够的RAM,则将“-j”设置为远高于核心数也可能是有意义的,因为这样整个过程的瓶颈就是I/O。 - Mikko Rantalainen

12

两种方法都没有错。为了让自己感到平静,并且与您正在编译的软件的作者和解(不同的多线程/单线程限制适用于软件本身),我建议您使用:

make -j`nproc`

注意:nproc 是Linux命令,用于返回系统上可用的核心/线程数(现代CPU)。将它放在如上所示的引号“`”下面,将数字传递给make命令。

附加信息:正如某些人提到的,使用所有的核心/线程来编译软件可能会让您的系统变得非常缓慢甚至无响应,并且甚至可能比使用较少的核心需要更长时间。我曾经看到一位Slackware用户发布了他拥有双核CPU,但仍然进行了高达j 8的测试,这在j 2处停止不再有区别(该CPU只能利用2个硬件核心)。因此,为了避免出现无响应的问题,建议您按照以下方式运行它:

make -j`nproc --ignore=2`

这将把nproc的输出传递给make,并从其结果中减去2个核心。


1
如果你要在脚本中编写这个命令,我建议使用语法make -j$(nproc --ignore=2),因为它比反引号更易于阅读。 - Mikko Rantalainen

7
最终,您需要进行一些基准测试来确定最佳构建使用的数量,但请记住,CPU并不是唯一重要的资源!
例如,如果您的构建严重依赖于磁盘,则在多核系统上生成大量作业实际上可能会更慢,因为磁盘将不得不额外工作,来回移动磁头以服务所有不同的作业(取决于许多因素,如操作系统如何处理磁盘缓存,磁盘本地命令队列支持等)。
然后,您有“真实”的核心与超线程。 您可能会从为每个超线程生成作业中受益,也可能不会。 再次,您需要进行基准测试才能找出答案。
我不能说我特别尝试过#cores + 1,但在我们的系统上(Intel i7 940,4个超线程核心,大量RAM和VelociRaptor驱动器)和我们的构建(大规模的C ++构建,交替CPU和I / O绑定),-j4和-j8之间几乎没有区别。(它可能好15%...但远非两倍好。)
如果我要去吃午饭,我会使用-j8,但如果我想在构建时使用我的系统进行其他任何事情,我会使用较低的数字。 :)

1
听起来很不错,但我不明白为什么你不直接使用“-j 8”每次都获得那个+15%。 - s g
2
@sg:在我原帖描述的系统上,j8 对机器确实是一种负担。虽然机器还是“可用”的,但响应速度明显变慢。因此,如果我仍想将其用于其他任务(通常是处理其他代码,可能偶尔进行单个 DLL 构建),我会为交互部分保留几个核心。 - ijprest
1
@sg:在我们的新系统上,这不再是一个问题...我怀疑这主要是因为我们现在正在运行SSD。 (我认为现在我们完全受到CPU限制,因为我们尝试使用RAM驱动器进行构建,但几乎没有改善。)但是,如果我在前台执行任何比简单文本编辑更复杂的操作,我仍然会留出几个核心。 - ijprest

5

我刚刚购买了一台带有Foxconn主板和4GB G-Skill内存的Athlon II X2 Regor处理器。

我在最后附上了我的规格信息,包括 'cat /proc/cpuinfo' 和 'free' 命令的输出,方便其他人查看。这是一款双核心的Athlon II x2处理器,配备4GB内存。

uname -a on default slackware 14.0 kernel is 3.2.45.

我将下一个步骤的内核源代码 (linux-3.2.46) 下载到 /archive4 目录;

解压它 (tar -xjvf linux-3.2.46.tar.bz2);

进入该目录 (cd linux-3.2.46);

并将默认内核的配置文件复制过来 (cp /usr/src/linux/.config .);

使用 make oldconfig 命令准备 3.2.46 内核的配置文件;

然后带有不同参数的 -jX 运行 make 命令。

我通过在 time 命令后面运行 make 命令(例如 'time make -j2')来测试每次运行的时间。在每次运行之间,我会执行 'rm -rf' 删除 linux-3.2.46 目录,并重新解压,复制默认的 /usr/src/linux/.config 到该目录中,运行 make oldconfig,然后再次进行 'make -jX' 的测试。

普通的 "make":

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

与前面相同,但使用 make -j2。
real    27m3.194s
user    48m5.135s
sys     3m39.431s
bob@Moses:/archive4/linux-3.2.46$

与上面相同,只是使用make -j3

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

与前面相同,但使用make -j4

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

与上述相同,但使用 make -j8

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

'cat /proc/cpuinfo' 的结果如下:

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

'free' 的结果为:

bob@Moses:/archive4$ free
             total       used       free     shared    buffers     cached
Mem:       3991304    3834564     156740          0     519220    2515308

1
在该系统上,make -j 是什么意思?Make 应该会检查负载并根据负载调整进程数量。 - docwhat
2
make -j doesn't limit the number of jobs at all. This is usually disastrous on a medium- or large-sized project as quickly more jobs are forked than can be supported by RAM.The option you need to restrict by load is -l [load], in conjunction with -j - Matt Godbolt

3
多年过去了,这些答案中的大部分仍然正确。然而,有一点变化:现在使用比物理核心更多的作业确实可以显著加快速度。作为对Dascandy表格的补充,这是我在Linux上使用AMD Ryzen 5 3600X编译项目的时间。(The Powder Toy,提交c6f653ac3cef03acfbc44e8f29f11e1b301f1ca2)
我建议你自己检查,但我发现与他人的意见相结合,在Zen上使用逻辑核心数量作为作业数量效果很好。此外,系统似乎没有失去响应性。我想这也适用于最近的英特尔CPU。请注意,我也有一个SSD,所以测试一下你自己的CPU可能是值得的。
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.)

在Ubuntu 19.10上进行的测试,使用了Ryzen 5 3600X处理器、Samsung 860 Evo固态硬盘(SATA)和32GB内存。这是一款6核12线程的Zen 2处理器,两个CCX之间共享2x 16MiB的L3缓存。(每个CCX有3个核心,共享一个16MiB的L3缓存。)
当作业数为6时,构建时间为27.5秒。速度一直提升到12个作业,即逻辑核心的数量,尽管11个作业的速度几乎一样快。对于12个作业与6个作业相比,速度提升了1.4倍(27.51 / 19.553)。正如预期的那样,结果在此之后趋于稳定。
最后注意:其他使用3600X处理器的人可能会获得比我更好的时间。在进行此测试时,我启用了节能模式,稍微降低了CPU的速度。

相关:

https://www.phoronix.com/review/amd-epyc-9754-smt/6 在拥有 128 个 Zen4c 核心的 AMD Bergamo 上测试了开启和关闭 SMT。使用 clang 和 GCC 进行编译时,开启 SMT 的情况下编译时间比关闭 SMT 的情况更长。(拥有充足的内存,并且源代码已经热存在磁盘缓存中,在无 SMT 构建中已经有足够的并行性,因为这是一个巨大的物理核心数量。甚至可能与某些目录中的源文件数量相比。)

Zen 4C 核心每个核心的 L3 缓存较少(每个 CCX 16 个核心而不是 8 个),这可能对 SMT 的影响更大。

此外,那台 EPYC 的 CPU 频率可能受到功耗限制,所以没有开启 SMT 的情况下 CPU 频率可能更高。在 "Eco" 模式下运行可能会改变这一点。


https://www.phoronix.com/review/amd-epyc-9754-smt/6 在一台搭载128个Zen4c核心的AMD Bergamo上测试了开启和关闭SMT。使用clang和GCC编译时,开启SMT与关闭SMT相比,编译时间更长。(由于有足够的内存,并且源代码已经热缓存在磁盘缓存中,并且在无SMT构建中已经有足够的并行性,因为这是一个庞大的物理核心数量。)Zen 4C核心每个核心的缓存比您系统上的完整核心少,所以超线程可能会更差。此外,物理核心数量可能与每个目录中的源文件数量相比较多。 - Peter Cordes

3

作为参考:

LKD中的“生成多个构建任务”部分:

n是要生成的作业数。通常的做法是每个处理器生成一个或两个作业。例如,在双处理器机器上,可以执行以下操作:

$ make j4


这是一个损坏的链接,这句话出自罗伯特·洛夫的《Linux内核开发》吗? - Behrooz
是的,它来自那本书。 - Nan Xiao

2

根据我的经验,增加额外的作业可以带来一些性能优势。这是因为磁盘 I/O 是除 CPU 外的瓶颈之一。然而,要决定增加多少个额外的作业并不容易,因为这与使用的 CPU 核心数量和磁盘类型高度相关。


1

是的!在我的3950x上,我运行-j32,它可以节省几个小时的编译时间!即使在编译期间,我仍然可以观看YouTube、浏览网页等,没有任何区别。即使使用1TB 970 PRO nvme或1TB Auros Gen4 nvme和64GB的3200C14,处理器也不总是被占用。即使它被占用了,我在用户界面上也没有注意到差异。我计划在不久的将来在一些大型项目中测试-j48。像你一样,我期望看到一些令人印象深刻的改进。那些仍然使用四核心的人可能不会获得同样的收益....

连Linus本人都升级到了3970x,你可以打赌他至少在运行-j64。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接