【问题标题】:GNU make: should the number of jobs equal the number of CPU cores in a system?GNU make:作业数量是否应该等于系统中 CPU 内核的数量?
【发布时间】:2011-01-30 17:47:45
【问题描述】:

对于 GNU make 中的作业数是否应该等于内核数,或者您是否可以通过添加一个可以排队的额外作业而其他作业来优化构建时间,似乎存在一些争议“工作”。

在四核系统上使用-j4-j5 更好吗?

您是否见过(或做过)任何支持其中之一的基准测试?

【问题讨论】:

  • 仅供参考,您可以使用make `nproc` 制作CPU独立脚本:)
  • 如果您混合了 io-bound 和 cpu-bound 的配方,那么您可能需要的不仅仅是 NCPU。还可以考虑添加 -lX 选项。这不是一个真正可以回答的问题,除了“这取决于你的硬件和制作任务。”
  • 技术上可以看到改进。你需要一个慢速磁盘,没有足够的内存和大量的小源代码文件。十年前更容易获得。

标签: makefile gnu-make


【解决方案1】:

我想说最好的办法是根据您的特定环境和工作负载自行对其进行基准测试。似乎有太多变量(源文件的大小/数量、可用内存、磁盘缓存、您的源目录和系统标头是否位于不同的磁盘上等),无法一刀切。

我的个人经验(在 2 核 MacBook Pro 上)是 -j2 明显快于 -j1,但除此之外(-j3、-j4 等)没有可测量的加速。因此,对于我的环境,“工作 == 核心数”似乎是一个很好的答案。 (YMMV)

【讨论】:

    【解决方案2】:

    我已经在我的 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 秒。优势是大约 6.5 秒,编译时间为 53.5 秒,加速了 12%。
    • 从线程数缩放到双线程数并没有显着加速。 12 和 15 的时间很可能是您可以忽略的统计异常。所花费的总时间和系统时间一样略有增加。两者都很可能是由于任务切换增加所致。这样做没有任何好处。

    我现在的猜测:如果您在计算机上执行其他操作,请使用核心数。如果不这样做,请使用线程数。超过它没有任何好处。在某些时候,它们将变得内存受限并因此崩溃,从而使编译速度变慢。 “inf”行是在很久以后添加的,这让我怀疑 8+ 工作存在一些热限制。这确实表明,对于这个项目大小,没有有效的内存或吞吐量限制。不过这是一个小项目,需要 8GB 内存进行编译。

    【讨论】:

    • 根据stackoverflow.com/questions/56272639/…,您可以获得比 CPU 更多的任务运行的优势,但前提是您的任务花费大量时间等待网络 I/O。但对于编译任务,情况并非如此。
    【解决方案3】:

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

    但是,我无法给出科学的解释:我见过很多人使用相同的设置,到目前为止他们给了我很好的结果。

    无论如何,您必须小心,因为某些 make-chains 根本与 --jobs 选项不兼容,并且可能导致意外结果。如果您遇到奇怪的依赖错误,请尝试 make 而不使用 --jobs

    【讨论】:

    • 解释(虽然不能保证它的科学性)是“+ 1”提供了一个额外的工作,它在其他 n 个工作中的任何一个都在做 I/O 时运行。
    • @LaurynasBiveinis:但是作业总是在不同的核心上运行,至少比在更保守的设置中更频繁地让作业有机会在同一个核心上停留更长时间一段的时间。这里各有利弊……
    • 核心数 + 1 也是我的默认设置。一个问题是,在任何相当大的系统中,make 似乎都会延迟链接并将所有链接步骤一起执行。此时你的内存用完了。呸!
    • 一些 make-chains 根本不兼容 --jobs 选项 -> 这意味着您缺少依赖项。如果你得到这个,请修复你的 makefile。
    【解决方案4】:

    最终,您必须进行一些基准测试以确定用于构建的最佳数量,但请记住,CPU 并不是唯一重要的资源!

    例如,如果您有一个严重依赖磁盘的构建,那么在多核系统上生成大量作业实际上可能更慢,因为磁盘必须做额外的工作来回移动磁盘头以服务于所有不同的作业(取决于许多因素,例如操作系统处理磁盘缓存的能力、磁盘对本机命令队列的支持等)。

    然后你就有了“真正的”核心与超线程。您可能会或可能不会从为每个超线程生成作业中受益。同样,您必须进行基准测试才能找到答案。

    我不能说我专门尝试过 #cores + 1,但在我们的系统(Intel i7 940、4 个超线程内核、大量 RAM 和 VelociRaptor 驱动器)和我们的构建 ( CPU 和 I/O 交替绑定的大规模 C++ 构建)-j4 和 -j8 之间几乎没有区别。 (它可能好 15%……但远没有两倍好。)

    如果我要出去吃午饭,我会使用 -j8,但如果我想在系统构建时将系统用于其他任何事情,我会使用较小的数字。 :)

    【讨论】:

    • 看起来不错,但我很困惑为什么你不会每次都使用 -j 8 来获得 +15% 的收益
    • @sg:j8 对我在原始帖子中描述的系统真的很费力......这台机器仍然可用,但它的响应速度肯定较慢。因此,如果我仍然想以交互方式将它用于其他任务(通常处理其他代码,并且可能偶尔会构建单 DLL),我会为交互位保留几个内核。
    • @sg:这在我们的新系统上不是问题……我怀疑这主要是因为我们现在正在运行 SSD。 (我认为现在我们要使用 SSD,我们完全受 CPU 限制......我们尝试完全在 RAM 驱动器上构建,几乎没有任何改进。)但如果我愿意,我仍然会留下几个内核空闲除了在前台进行简单的文本编辑之外,还可以做任何事情。
    【解决方案5】:

    两者都没错。为了与您自己和您正在编译的软件的作者保持和平(不同的多线程/单线程限制适用于软件级别本身),我建议您使用:

    make -j`nproc`
    

    注意:nproc 是 linux 命令,它将返回系统上可用的内核/线程数(现代 CPU)。将它放在上面的记号`下面会将数字传递给 make 命令。

    附加信息:正如有人提到的那样,使用所有内核/线程来编译软件可能会让你的机器几乎死机(无响应),甚至可能比使用更少的内核花费更长的时间。正如我在这里看到的一位 Slackware 用户发布的那样,他有双核 CPU,但仍然提供高达 j 8 的测试,这在 j 2 时不再不同(CPU 只能使用 2 个硬件内核)。因此,为避免出现无响应的框,我建议您像这样运行它:

    make -j`nproc --ignore=2`
    

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

    【讨论】:

      【解决方案6】:

      我刚得到一个带有富士康 M/B 和 4GB G-Skill 内存的 Athlon II X2 Regor proc。

      我将我的“cat /proc/cpuinfo”和“free”放在最后,以便其他人可以看到我的规格。它是具有 4GB RAM 的双核 Athlon II x2。

      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 到目录 (cd linux-3.2.46);

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

      使用make oldconfig准备3.2.46内核配置;

      然后使用 -jX 的各种咒语运行 make。

      我通过在 time 命令之后发出 make 来测试每次运行的时间,例如, '时间使-j2'。在每次运行之间我'rm -rf' linux-3.2.46 树并重新提取它,将默认的 /usr/src/linux/.config 复制到目录中,运行 make oldconfig 然后再次进行我的'make -jX'测试.

      简单的“制作”:

      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
      

      “免费”产量:

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

      【讨论】:

      • make -j 在那个系统上做了什么? Make 应该检查负载并根据负载扩展进程数。
      • make -j 根本不限制作业的数量。这对于中型或大型项目来说通常是灾难性的,因为很快就会有更多的工作被分叉,而不是 RAM 所能支持的。您需要通过负载限制的选项是-l [load],与-j一起使用
      【解决方案7】:

      作为参考:

      来自LKD 中的Spawning Multiple Build Jobs 部分:

      其中 n 是要生成的作业数。通常的做法是为每个处理器生成一个或两个作业。例如,在双处理器机器上,可能会这样做

      $ 制作 j4

      【讨论】:

      • 断开的链接,这是来自 Robert Love 的 Linux Kernel Development 的引述吗?
      • 是的,来自那本书。
      【解决方案8】:

      根据我的经验,添加额外作业时一定会带来一些性能优势。 只是因为磁盘 I/O 是除 CPU 之外的瓶颈之一。然而,决定额外作业的数量并不容易,因为它与所用磁盘的核心数量和类型高度相关。

      【讨论】:

        【解决方案9】:

        多年后,这些答案中的大多数仍然是正确的。但是,发生了一些变化:使用比物理内核更多的作业现在可以提供真正显着的加速。作为 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.)
        

        在配备 Ryzen 5 3600X、三星 860 Evo SSD (SATA) 和 32GB RAM 的 Ubuntu 19.10 上完成的测试

        最后说明:其他拥有 3600X 的人可能会比我更好。在做这个测试时,我启用了 Eco 模式,稍微降低了 CPU 的速度。

        【讨论】:

          【解决方案10】:

          是的!在我的 3950x 上,我运行 -j32,它可以节省数小时的编译时间!我仍然可以在编译期间观看 youtube、浏览网页等,没有任何区别。即使使用 1TB 970 PRO nvme 或 1TB Auros Gen4 nvme 和 64GB 的 3200C14,处理器也不总是固定的。即使是这样,我也没有注意到 UI 明智。我计划在不久的将来在一些即将到来的大项目中使用 -j48 进行测试。正如您可能所做的那样,我希望看到一些令人印象深刻的改进。那些仍然拥有四核的人可能不会获得相同的收益....

          Linus 自己刚刚升级到 3970x,你可以赌你的底钱,他至少在运行 -j64。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-04-23
            • 1970-01-01
            • 1970-01-01
            • 2017-10-26
            • 2015-08-17
            • 1970-01-01
            相关资源
            最近更新 更多