我们店里有一台机器,具有以下特点:
- 256 core sparc solaris
- ~64gb 内存
- 其中一些内存用于 /tmp 的 ram 驱动器
回到它最初设置的时候,在其他用户发现它的存在之前,我运行了一些计时测试,看看我能把它推多远。有问题的构建是非递归的,因此所有作业都从一个制作过程开始。我还将我的 repo 克隆到 /tmp 以利用内存驱动器。
我看到了到 -j56 的改进。除此之外,我的结果与 Maxim 的图表非常相似,直到高于(大约)-j75 的某个地方,性能开始下降。运行多个并行构建,我可以将它推到 -j56 的明显上限之外。
主make进程是单线程的;在运行了一些测试之后,我意识到我遇到的上限与主线程可以服务多少子进程有关——这进一步受到了 makefile 中任何需要额外时间来解析的东西的阻碍(例如,使用=而不是:=,以避免不必要的延迟评估、复杂的用户定义宏等)或使用$(shell)之类的东西。
这些是我已经能够做的事情,以加快产生显着影响的构建:
尽可能使用:=
如果您使用:= 分配给变量一次,然后使用+=,它将继续使用立即评估。但是,?= 和 +=,如果以前没有分配过变量,总是会延迟评估。
在您有足够大的构建之前,延迟评估似乎没什么大不了的。如果一个变量(如 CFLAGS)在所有 makefile 都被解析后没有改变,那么你可能不想对它使用延迟评估(如果你这样做,你可能已经无论如何都知道我在说什么,所以无视我的建议)。
如果您创建使用$(call) 工具执行的宏,请尽量提前进行评估
我曾经想到要创建以下形式的宏:
IFLINUX = $(strip $(if $(filter Linux,$(shell uname)),$(1),$(2)))
IFCLANG = $(strip $(if $(filter-out undefined,$(origin CLANG_BUILD)),$(1),$(2)))
...
# an example of how I might have made the worst use of it
CXXFLAGS = ${whatever flags} $(call IFCLANG,-fsanitize=undefined)
此构建生成超过 10,000 个目标文件,其中大约 8,000 个来自 C++ 代码。如果我使用了CXXFLAGS := (...),它只需要在所有编译步骤中立即用已经评估的文本替换${CXXFLAGS}。相反,它必须为每个编译步骤重新评估该变量的文本一次。
如果您别无选择,至少可以帮助减轻一些重新评估的替代实现:
ifneq 'undefined' '$(origin CLANG_BUILD)'
IFCLANG = $(strip $(1))
else
IFCLANG = $(strip $(2))
endif
...虽然这只有助于避免重复的$(origin) 和$(if) 调用;您仍然必须尽可能遵循有关使用 := 的建议。
尽可能避免在配方中使用自定义宏
上面的推理应该很明显了;任何需要为每个编译/链接步骤重复评估变量或宏的东西都会降低您的构建速度。每个宏/变量评估都发生在启动新作业的同一线程中,因此任何花费在解析上的时间都会导致延迟启动另一个并行作业。
每当它促进代码重用和/或提高可读性时,我都会在自定义宏中添加一些配方,但我尽量将其保持在最低限度。