【问题标题】:How can I write a makefile to auto-detect and parallelize the build with GNU Make?我如何编写一个 makefile 来使用 GNU Make 自动检测和并行化构建?
【发布时间】:2010-03-26 23:42:38
【问题描述】:

不确定这是否可能仅在一个 Makefile 中实现,但我希望以一种方式编写 Makefile,以便尝试在文件中构建任何目标自动检测当前系统上的处理器数量并构建并行目标的处理器数量。

类似于下面的“伪代码”示例,但更简洁?

all:
    @make -j$(NUM_PROCESSORS) all

或者:

all: .inparallel
    ... build all here ...

.inparallel:
    @make -j$(NUM_PROCESSORS) $(ORIGINAL_TARGET)

在这两种情况下,您只需输入:

% make all

希望这是有道理的。

更新:仍然希望上面有一个示例 Makefile。对查找进程数不是很感兴趣,但对如何编写一个生成文件以在没有 -j 命令行选项的情况下并行构建感兴趣。

【问题讨论】:

  • 不要试图在一个 Makefile 中进行。据我所知,标准技术是有一个简单的顶级 Makefile 调用另一个,在这种情况下使用类似 $(MAKE) -j $(NUM_PROCESSORS) -f $(THE_REAL_MAKEFILE) 的东西。
  • 真正的解决方案是使用基于更强大的流程建模技术的 make 替代方案,但我不知道有什么好的候选者(我为该技术做,但不是为工具)。
  • this unix.stackexchange.com reply 所示,只需使用 make -j$(nproc)

标签: makefile parallel-processing


【解决方案1】:

检测部分将取决于操作系统。这是一个适用于 Linux 和 Mac OS X 的片段:

NPROCS:=1
OS:=$(shell uname -s)

ifeq($(OS),Linux)
  NPROCS:=$(shell grep -c ^processor /proc/cpuinfo)
endif
ifeq($(OS),Darwin) # Assume Mac OS X
  NPROCS:=$(shell system_profiler | awk '/Number Of CPUs/{print $4}{next;}')
endif

要使其正常工作,您可能必须重新调用 make。那么你的问题是防止无限递归。您可以通过拥有两个 makefile 来管理它(第一个只重置 -j 值),但可能可以对其进行微调。

【讨论】:

  • 你可以使用环境变量来解决递归问题……Linux内核构建系统就是一个很好的例子
  • 我更好奇如何实现递归,而不是检测 cpu 的数量,这与 IMO 相比非常简单。
  • @Sam 我对内核构建系统有些熟悉。你能告诉我在构建系统的什么地方我可以找到这个特定的用法吗?
  • NPROCS:=$(NUMBER_OF_PROCESSORS) 在windows上,继承自同名的自动环境变量。
  • 对于 Mac OS X,您可以通过将信息限制为仅硬件数据来改进 system_profiler 的使用 - 例如。 system_profiler SPHardwareDataType。然后你可以使用与上面类似的命令:system_profiler SPHardwareDataType | awk '/Total Number of Cores/{print $5}{next;}'。尽管您可以使用sysctl -n hw.ncpu,但不要使用system_profiler,只需意识到它可能会报告一些不同的情况,因为它还包括超线程内核。对于我的 13" Intel Core i5(2015 年初),system_profiler 报告 2 个内核,而 sysctl 报告 4 个。您可以使用 sysctl -n hw.physicalcpu
【解决方案2】:

我刚刚将它添加到我的 Makefile 的顶部。它允许 make 创建任意数量的作业,但会尝试将平均负载保持在 CPU 核心数以下。

MAKEFLAGS+="-j -l $(shell grep -c ^processor /proc/cpuinfo) "

请注意,这是特定于 Linux 的。

【讨论】:

  • 不幸的是,这种方法并不适用于所有情况,因此请在使用前仔细试验。我已经对其进行了试验,并让它在太多并行作业和串行构建之间大幅波动,直到平均负载降至阈值以下。
  • 甚至更短:MAKEFLAGS+="-j $(shell nproc)" - 请注意,您可能很容易用它来打自己的脚
  • 请注意,这在 GNU Make 版本 4.2 中不起作用。任何旧版本或 4.3 版都应该没问题。详情请见savannah.gnu.org/bugs/?48274
  • 我更喜欢JOBS ?= $(shell nproc),然后是MAKEFLAGS += -j $(JOBS) -l $(JOBS)
【解决方案3】:

这是我的选择:

ifeq ($(OS),Linux)
        NUMPROC := $(shell grep -c ^processor /proc/cpuinfo)
else ifeq ($(OS),Darwin)
        NUMPROC := $(shell sysctl hw.ncpu | awk '{print $$2}')
endif

# Only take half as many processors as available
NUMPROC := $(shell echo "$(NUMPROC)/2"|bc)

ifeq ($(NUMPROC),0)
        NUMPROC = 1
endif 

【讨论】:

  • 不是通过awk 传递sysctl 输出,而是仅询问值:sysctl -n hw.ncpu
  • 确定操作系统(适用于旧版 Linux 和 Mac)的最简单方法是 NUMPROC=`getconf _NPROCESSORS_ONLN`
【解决方案4】:

在浏览了 LDD3 第 2 章并阅读了 dmckee 的答案后,我想出了使用两个 makefile 的不太好的答案(我宁愿只使用一个)。

$ cat Makefile
MAKEFLAGS += -rR --no-print-directory

NPROCS := 1
OS := $(shell uname)
export NPROCS

ifeq ($J,)

ifeq ($(OS),Linux)
  NPROCS := $(shell grep -c ^processor /proc/cpuinfo)
else ifeq ($(OS),Darwin)
  NPROCS := $(shell system_profiler | awk '/Number of CPUs/ {print $$4}{next;}')
endif # $(OS)

else
  NPROCS := $J
endif # $J

all:
    @echo "running $(NPROCS) jobs..."
    @$(MAKE) -j$(NPROCS) -f Makefile.goals $@

%:
    @echo "building in $(NPROCS) jobs..."
    @$(MAKE) -j$(NPROCS) -f Makefile.goals $@
$ cat Makefile.goals
MAKEFLAGS += -rR --no-print-directory
NPROCS ?= 1

all: subgoal
    @echo "$(MAKELEVEL) nprocs = $(NPROCS)"

subgoal:
    @echo "$(MAKELEVEL) subgoal"

你觉得这个解决方案怎么样?

我看到的好处是人们仍然可以输入make 来构建。因此,没有一些“驱动程序”脚本可以完成 NPROCSmake -j$(NPROCS) 的工作,人们必须知道而不是输入 make。

缺点是您必须明确使用make -f Makefile.goals 才能进行串行构建。而且我不确定如何解决这个问题......

更新:将 $J 添加到上述代码段。似乎工作做得很好。尽管它有两个 makefile 而不是一个,但它仍然非常无缝且有用。

【讨论】:

  • 我非常喜欢这个...我不会太担心串行构建,尽管您可以尝试更多的环境变量调整。像“J=1 make”这样的东西更短。
  • 啊,是的,我想我可以做到make J=1,这很简单。
  • 如果你把Makefile.goals中的all改成goal,然后把两个文件合并到一起,再调用@$(MAKE) -j$(NPROCS) $@,不行吗?
  • NPROCS:=$(NUMBER_OF_PROCESSORS) 在windows上,继承自同名的自动环境变量。您必须以不同的方式检测操作系统。 %OS% / $(OS) 存在。
【解决方案5】:

我将跳过 $(NPROCS) 检测内容,但这里是您可以在单个 Makefile 中执行此操作的方法(这可能是 GNU Make 特定的,但看起来像您正在运行的变体):

ifeq ($(NPROCS),)
# Code to set NPROCS...
%:
    $(MAKE) -j$(NPROCS) NPROCS=$(NPROCS)
else
# All of your targets...
endif

参见GNU Make Manual 中的Defining Last-Resort Default RulesOverriding Part of Another Makefile

【讨论】:

    【解决方案6】:

    如果我正确阅读了这个问题,那么目标是尽可能地并行化构建过程。 make 手册页声明如下

    如果 -j 选项不带参数给出,make 将不会限制可以同时运行的作业数量。

    这基本上不是您想要的解决方案吗?如果您的 Makefile 有足够的并行目标,您将使用所有 CPU,并且如果目标不并行,那么 -j 选项无论如何也无济于事。

    【讨论】:

    • 如果你有数百个源文件,这相当于 fork 炸弹。
    • 这似乎是 Eclipse 在使用并行构建选项“使用最佳作业编号”时所做的事情
    【解决方案7】:

    如果您希望它是自动的,那么您可以覆盖您的典型 make 命令,使其成为您主目录中 .bashrc 中自身的别名。

    例子:

    alias make="make -j"
    

    或者你可以这样做:

    alias jmake="make -j"
    

    如果您不想覆盖它,但想要一种快速且简单(且令人难忘)的方式来并行运行 make。

    【讨论】:

    • 这个想法是让它适用于任何使用源代码的人,而不需要更改每个开发人员的环境。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-16
    • 1970-01-01
    • 2017-08-25
    • 2013-10-05
    • 1970-01-01
    相关资源
    最近更新 更多