Ninja: 启发式:在输入较小的作业之前运行输入较大的作业

创建于 2016-07-15  ·  14评论  ·  资料来源: ninja-build/ninja

在 LLVM 构建中,我们有一些文件需要比其他文件更长的时间来编译。 其中一个的文件名——一个 30,000 行的 C++ 文件(不要问)——按字母顺序排列很晚,而且它通常在大多数其他目标完成构建后很长时间才结束。

如果忍者可以启发式地查看规则输入的组合大小并首先运行具有更大输入的规则,那将是很酷的。

feature

最有用的评论

Ninja 确实节省了工作时间,所以它有这些信息。 第一次构建可能不是最佳的,但由于 ninja deps 日志也没有填充,因此情况已经如此。

所有14条评论

我不确定编译时间是文件大小的唯一函数。我可以想象一个小文件对于编译器来说是一个难以破解的难题,而一个大文件是微不足道的。

我不确定编译时间是文件大小的唯一函数..

它不是。 尽管如此,编译时间肯定与文件大小正相关。 因此,这种启发式的好处。

也可能忍者可以做某种 PGO(配置文件引导优化):+1:比如收集时间信息,编译每个文件需要多长时间,并使用此信息在下一次构建中确定文件的优先级。

尽管如此,编译时间肯定与文件大小正相关。

我认为任何基于源文件的启发式方法都没有意义。 例如,我们有不到一千行的 C++ 文件,它编译了将近一分钟(不要问)。 最终,这取决于预处理器文件的难度以及在编译期间它将实例化多少模板。

当然可以假设 30k 行文件的编译时间比 1k 长,但我认为这不是 ninja 应该做出的决定。

比如收集时间信息需要多长时间来编译每个文件......

这已经在 .ninja_log 中完成了您可以使用此脚本https://github.com/nico/ninjatracing来解析它并在 chrome 中加载以查看构建过程是如何进行的。

...并使用此信息在下一次构建中确定文件的优先级。

所以既然我们已经有了构建时间信息,忍者确实可以在下一个构建中使用该信息。 实际上可以提供帮助。

编辑:

好的,我实际上已经查看了编译时间。 编译时间最长的文件是https://github.com/llvm-mirror/clang/blob/master/lib/ASTMatchers/Dynamic/Registry.cpp ,它只有 600 行。 因此,文件大小指标对这个特定文件根本不起作用。

llvm_buiild

我也没有看到这个过程中有任何瓶颈。 但可能有助于我用 13 个线程构建。

嗨,如果不清楚,我实际上在 LLVM 和 clang 上工作。 在过去的几周里,我一直在优化 clang/llvm 后端的速度,实际上正是在这个过程中我遇到了这个问题。 你说得对,干净地重建clang包很好。 但是,如果您触摸某些标头,您很容易最终等待这些非常长的编译之一,例如 x86 isel 降低(这也恰好出现在字母表中的后期,加剧了问题)。

上面的分析没有统计意义,因为它只查看一个文件。 这是我的机器上的 cpp 文件大小(x 轴)与编译时间(y 轴)的散点图,用于进行干净的 clang 构建。

如果文件大小和编译时间之间没有相关性,我们会认为这看起来像随机噪声。 但正如您所见,文件大小和编译时间之间存在良好的相关性。

plt

无论如何,认为使用过去的编译时间来通知排序是一个好主意。

当然有相关性。 但我认为它不足以对每个项目都具有普遍性。 我没有可用的数据,所以我可能在这里错了。

另见 #60 #376 #232

但我认为它不足以对每个项目都具有普遍性。

也许我对我的建议不清楚。

建议是,_如果您要在构建 A 和 B_ 之间做出任意选择,那么可能使用一些启发式方法来确定构建哪个目标需要更长的时间。 “看看他们上次构建花了多长时间”的启发式方法很棒,比我查看文件大小的建议要好得多。 但是在没有历史构建时间的情况下,我希望文件大小仍然有用。

同样,如果适用更好的启发式(例如您提到的启发式之一),您应该使用它。 但由于我只是建议将此启发式作为最后的手段应用,我认为它不是“通用”的事实并没有什么不同。 我们将做出一个随意的选择,现在我们做出一个稍微不那么随意的选择。

无论如何查看历史构建时间要好得多,我真的不在乎您是否查看文件大小。 :)

Ninja 有一种在构建 A 和 B 之间进行任意选择的启发式方法:它构建清单中的第一个。 你能把你的启发式方法放在你的 build.ninja 生成器中吗?

@colincross这对于初始排序来说很好,但是后续排序可以使用比生成器希望的更准确的时间。 就个人而言,我不太担心初始构建时间,但生成器可以通过这种方式进行优化。

我认为使用文件大小可能不足以“分析”。 例如,一个包含很多包含的非常小的 cpp 文件可能需要很长时间才能处理。 非编译过程(解析、复制、文件生成……)与文件大小无关。
不过,对于某些构建单元来说,这仍然是一个好方法。
第二种方法可能是将构建某些文件所需的时间保存在缓存中。 可以通过分析此缓存来优化重建。

Ninja 确实节省了工作时间,所以它有这些信息。 第一次构建可能不是最佳的,但由于 ninja deps 日志也没有填充,因此情况已经如此。

@jlebar
我与 https://go.googlesource.com/gollvm/ 有类似的东西:在特定的源文件强制编译器崩溃之前,我得到了数千个已编译的源文件,其中包含“内存不足”错误。
目前有两个文件导致这样的失败:
https://github.com/llvm/llvm-project/blob/7d3aace3f52f6b3f87aac432aa41ae1cdeb348eb/llvm/lib/Target/AMDGPU/AMDGPULowerIntrinsics.cpp
https://github.com/llvm/llvm-project/blob/7d3aace3f52f6b3f87aac432aa41ae1cdeb348eb/llvm/lib/Target/AArch64/AArch64PreLegalizerCombiner.cpp
虽然我对 Golang 的编译器前端与 OpenCL 或 OpenGL 有任何关系(目前后端没有任何相关的现有事件)这一事实有一些疑问 - 但对于一般 CPU 编程,存在 ARM64 支持。
在任何情况下,我都必须在 CMake 的配置中禁用 LLVM 项目的某些部分。
然而,第一个文件是 2934 个文件中的 697 个(过去的编译过程保留了已编译的 .o 文件),而第二个文件(在干净的构建中)似乎是 3214 个文件中的 817 个。
最好先编译其他文件,所以我会预测性地停留在那些会消耗我大部分 RAM 的文件上。 所以我有兴趣把它们放在最后——看看哪些有问题,因为在我尝试之前我不知道。 或者我可以吗?

伊万

@advancedwebdeveloper确实,在 Ninja 中有一种假设,即您有足够的 RAM 可以与您选择的-jN设置并行编译所有内容。 从根本上说,这个问题不是由 Ninja 选择的顺序引起的,而是因为 Ninja(以及我知道的所有其他构建工具)只为您提供了一种非常粗略的方法来限制并行性——即-j标志。

此页面是否有帮助?
0 / 5 - 0 等级