【问题标题】:GNU make's -j optionGNU make 的 -j 选项
【发布时间】:2010-12-06 13:23:22
【问题描述】:

自从我了解 -j 后,我就愉快地使用了 -j8。前几天我正在编译一个 atlas 安装,但 make 失败了。最终,我将其归结为乱序制作的东西——一旦我回到单线程制作,它就可以正常工作。这让我很紧张。在编写自己的 make 文件时需要注意哪些条件以避免使用 make -j 做一些意想不到的事情?

【问题讨论】:

  • 你确定是make的错吗?编写正确的 makefile 容易出错。
  • 嗯,即使是自动工具生成的 makefile 也有问题。尝试使用 -j2 或更高版本编译 GCC。

标签: gnu-make


【解决方案1】:

我认为 make -j 会尊重您在 Makefile 中指定的依赖项;也就是说,如果您指定 objA 依赖于 objB 和 objC,那么在 objB 和 objC 完成之前,make 不会开始处理 objA。

很可能您的 Makefile 没有足够严格地指定必要的操作顺序,幸运的是它恰好在单线程情况下为您工作。

【讨论】:

  • 没错。我的代码库大约有 2000 万行,主要是 C 语言和一点 C++。它分为数百个组件,其中大约一半使用 make,一半使用 jam。我总是使用 -j 选项进行并行编译;否则,构建将需要数小时。 Jam 会生成自己的依赖项,因此使用它的组件总是会成功。但是使用手工构建的 makefile 的组件有时会因为依赖不足而阻塞。
  • 我在网上看到很多 -j 指的是你想要编译多少 CPU。我已经在我的 8 核机器上运行了一个测试,make 可以工作到并包括-j8(除此之外,所有内核的使用率为 100%,类似于-j8)。在每个整数增量中,我会看到另一个核心使用率大约增加 100%。您认为这是一个很好的经验法则,可以知道将什么指定为您的 -j 值?
  • 我知道的唯一一个好的经验法则是在你的程序上做一个“make clean ; time make -jn”,使用不同的 (n) 值,看看每个构建需要多长时间,并且然后选择在最短的时间内完成的任何设置。变量太多(CPU 速度、RAM 速度、RAM 大小、硬盘驱动器速度、操作系统进程开销等)无法做出有用的概括。
  • 如果你使用-j参数而不提供任何整数值,make将使用系统cpu核心数,没有任何限制,等于/proc/cpuinfo中的最高处理器数。
  • 我喜欢使用time,但你们中的一些人可能会发现make -j [N] --debug=j 很有用,可能还有time。这会将作业信息添加到调试中,包括具有相应 PID 的添加/活动/收获目标。
【解决方案2】:

简而言之 - 确保您的依赖项正确且完整。

如果您使用的是单线程 make,那么您可能会盲目地忽略目标之间的隐式依赖关系。 使用并行 make 时,您不能依赖隐式依赖项。它们都应该明确。这可能是最常见的陷阱。特别是如果使用 .phony 目标作为依赖项。

This 链接是有关并行制作的一些问题的很好的入门读物。

【讨论】:

  • +1 对于良好的链接。我现在觉得我也可以信任 make -j 以及如何在出现问题时解决问题。值得一读。
【解决方案3】:

这是我开始使用并行构建时遇到的一个问题示例。我有一个名为“新鲜”的目标,我用它从头开始重建目标(“新鲜”构建)。过去,我通过简单地指示“干净”然后“构建”作为依赖项来编写“新鲜”目标。

build: ## builds the default target
clean: ## removes generated files
fresh: clean build ## works for -j1 but fails for -j2

在我开始使用并行构建之前效果很好,但是对于并行构建,它会尝试同时进行“清理”和“构建”。所以为了保证正确的操作顺序,我将“新鲜”的定义修改如下。

fresh:
    $(MAKE) clean
    $(MAKE) build

这基本上只是正确指定依赖关系的问题。诀窍是并行构建比单线程构建更严格。我的示例演示了给定目标的依赖项列表不一定指示执行顺序。

【讨论】:

  • 递归制作,哇! please always do clean before build 的正确说法当然是 build: clean.
  • @bobbogo:我不想总是在构建之前进行清理——在大多数情况下这是不必要的。我描述的“新鲜”目标基本上只是一个简单的脚本,它运行“make clean”,然后是“make build”(对于那些我想这样做的罕见情况)。在这种情况下,我认为递归执行 make 没有任何害处。
  • 用条件保护它怎么样:ifeq ($(MAKECMDGOALS), fresh); build: clean; endif(用换行符替换分号)?
  • @eriktous:有趣的想法。谢谢!
  • 条件是朝着正确方向迈出的一步,但我建议任何一天auto-dependency generation。无需递归制作或清洗。如果我更改了目标引用的任何文件或目标的任何先决条件,make 将创建必须重建的所有内容的有向无环图,允许任何make -j N 成功(包括仅make -j。)
【解决方案4】:

如果你有一个递归的make,事情就很容易坏掉。如果您不进行递归 make,那么只要您的依赖项正确且完整,您就不会遇到任何问题(除了 make 中的错误)。请参阅 Recursive Make Considered Harmful 以更全面地描述递归 make 的问题。

【讨论】:

  • > "为了避免这些症状,只需要避免分离;对整个项目使用单个 Makefile。"说真的……我猜这些人从来不需要在真正的生产环境中工作。
【解决方案5】:

最好有一个自动化测试来测试所有 make 文件的 -j 选项。即使是最优秀的开发人员也会在 make 的 -j 选项上遇到问题。最常见的问题是最简单的。

myrule: subrule1 subrule2
     echo done

subrule1:
     echo hello

subrule2:
     echo world

在正常的 make 中,你会看到 hello -> world -> done。 使用 make -j 4,你可能会看到 world -> hello -> done

我看到这种情况发生最多的地方是创建输出目录。例如:

build: $(DIRS) $(OBJECTS)
     echo done

$(DIRS):
     -@mkdir -p $@

$(OBJECTS):
     $(CC) ...

【讨论】:

    【解决方案6】:

    只是想我会添加到 subsetbrew 的答案中,因为它没有清楚地显示效果。但是添加一些睡眠命令可以。好吧,它可以在 linux 上运行。

    然后运行 ​​make 显示差异:

    • 制作
    • make -j4

    all: toprule1
    
    toprule1: botrule2 subrule1 subrule2
        @echo toprule 1 start
        @sleep 0.01
        @echo toprule 1 done
    
    subrule1: botrule1
        @echo subrule 1 start
        @sleep 0.08
        @echo subrule 1 done
    
    subrule2: botrule1
        @echo subrule 2 start
        @sleep 0.05
        @echo subrule 2 done
    
    botrule1:
        @echo botrule 1 start
        @sleep 0.20
        @echo "botrule 1 done (good prerequiste in sub)"
    
    botrule2:
        @echo "botrule 2 start"
        @sleep 0.30
        @echo "botrule 2 done (bad prerequiste in top)"
    

    【讨论】:

      猜你喜欢
      • 2021-12-24
      • 2021-05-27
      • 2010-11-17
      • 1970-01-01
      • 1970-01-01
      • 2014-06-28
      • 2016-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多