【问题标题】:How to speed up compilation time in linuxlinux下如何加快编译速度
【发布时间】:2013-07-19 10:11:15
【问题描述】:

在 linux 下编译时,我使用标志 -j16,因为我有 16 个内核。我只是想知道使用-j32之类的东西是否有意义。实际上,这是一个关于处理器时间调度的问题,以及是否有可能以这种方式对特定进程施加比其他任何方式更大的压力(假设我喜欢使用 -j16 进行并行编译,如果使用 -j32 会怎样?) . 我认为这没有多大意义,但我不确定,因为不知道内核如何解决这些问题。

亲切的问候,

【问题讨论】:

  • 你用的是什么编译器?尝试使用 -O3 标志
  • 我使用 gcc 作为编译器

标签: linux compiler-construction kernel


【解决方案1】:

我使用基于 GNU make 的非递归构建系统,我想知道它的扩展性如何。

我在具有超线程的 6 核 Intel CPU 上运行基准测试。我使用-j1-j20 测量了编译时间。对于每个-j 选项make 运行3 次并记录最短时间。使用-j9 可以缩短编译时间,比-j6 好11%。

换句话说,超线程确实有一点帮助,英特尔处理器具有超线程的最佳公式是number_of_cores * 1.5

Chart data is here.

【讨论】:

    【解决方案2】:

    经验法则是使用处理器数+1。超线程很重要,因此具有 HT 的四核 CPU 应该具有 -j9

    将值设置得太高会适得其反,如果您确实想加快编译时间,请考虑使用 ccache 缓存在每次编译中不会更改的编译对象,并使用 distcc 将编译分布到多台机器上。

    【讨论】:

    • 请问您的经验法则的基础是什么?
    • 多年或运行Gentoo Linux。这是他们文档中的一个示例:en.gentoo-wiki.com/wiki/… 请注意,这与 Gentoo 无关,它是一个 make 选项。
    • 您有任何数据支持您的说法吗? My empirical results 不同意你的经验法则。
    • 您的经验结果与我所说的并不矛盾,只是一个带有示例代码的系统的示例。在您的情况下,仍然是“经验法则”(核心数+1,如果启用了超线程,则核心数翻倍)将是 -j13,它仅比您指出的最佳 -j9 慢 0.5 秒。跨度>
    • 我同意你关于包含资源的说法,但这不是论文,它是一个帮助社区网站。最好在发布问题的 5 秒内包含一个完成 90% 的答案,而不是等待几个小时才能完成一个 100% 的答案。如果我的答案是错误的,我会删除它,事实并非如此。一周后继续讨论是没有意义的。这是我对此讨论的最后回复。
    【解决方案3】:

    我们店里有一台机器,具有以下特点:

    • 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) 调用;您仍然必须尽可能遵循有关使用 := 的建议。

    尽可能避免在配方中使用自定义宏

    上面的推理应该很明显了;任何需要为每个编译/链接步骤重复评估变量或宏的东西都会降低您的构建速度。每个宏/变量评估都发生在启动新作业的同一线程中,因此任何花费在解析上的时间都会导致延迟启动另一个并行作业。

    每当它促进代码重用和/或提高可读性时,我都会在自定义宏中添加一些配方,但我尽量将其保持在最低限度。

    【讨论】:

    • 有趣的观察。 Ninja 的主要前提是删除所有那些在开始检测需要重新编译的过时目标之前总是发生的扩展和 shell 步骤。但是,我还没有看到 ninja 优于使用最佳实践编写的非递归 make。一个好的测试是,当每个目标都是最新的时,什么都不构建需要多长时间。对于一个拥有超过 10,000 个源文件的 C++ 项目,我自己的非递归 make 花费不到 1 秒。
    猜你喜欢
    • 2014-06-10
    • 2014-07-24
    • 1970-01-01
    • 2011-04-03
    • 2010-11-03
    • 1970-01-01
    • 2017-06-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多