MSVC10 /MP构建在项目的不同文件夹之间不能多核处理

3
我希望有人能指出我们正在经历的问题或提供解决方法。
使用 /MP 编译项目时,仅同一文件夹中的文件会并发编译。我使用进程资源管理器切换命令行并确认了这种行为。
项目过滤器似乎对并发编译没有影响。
项目在磁盘上的结构如下:
Folder\
  project.vcxproj  
  source\  
    foo.cpp  
    foo1.cpp  
  other_folder\  
    bar.cpp
    bar1.cpp
    bar3.cpp

初始进程树:

MSBuild.exe
  cl.exe  ( passed: source\foo.cpp source\foo1.cpp )
    cl.exe  ( passed: source\foo.cpp )
    cl.exe  ( passed: source\foo1.cpp )

在2个cl.exe子实例完成后,父进程关闭,接下来出现以下进程树:

MSBuild.exe
  cl.exe  ( passed: other_folder\bar.cpp other_folder\bar1.cpp other_folder\bar2.cpp )
    cl.exe  ( passed: other_folder\bar.cpp )
    cl.exe  ( passed: other_folder\bar1.cpp )
    cl.exe  ( passed: other_folder\bar2.cpp )

我们的源代码以多层嵌套文件夹的形式组织,与磁盘上的标题布局相匹配 - 我不想放弃这一点来利用/MP。

这不可能是你所看到的。基于并发构建项目,而非源文件。你应该看到一个 cl.exe 实例同时构建两个 .cpp 文件。如果项目之间没有任何依赖关系(不太可能),那么只有这两个项目会同时被构建。 - Hans Passant
你试过了吗?你熟悉 /MP 吗?我知道可以并行构建项目,但这不是我要问的。 - Zac
你的MSBuild日志中显示了什么?我在我的机器上创建了一个新的VS 2010 C++项目,使用你上面概述的目录结构,将所有C++文件添加到项目中,并使用MSBuild从命令行构建。MSBuild记录了一次CL.exe的执行,并使用/MP选项将所有文件(来自两个目录)传递到同一条命令行中。 - Bilal
@Bilal,我会在周一尝试一下,看看结果如何。 - Zac
4个回答

3
在vcxproj XML中的“Object File Name”(在CL.exe命令行上的/Fo)中使用%(RelativeDir)将导致msbuild基于每个目录将cpp文件批处理到cl.exe上。这可能会对使用/MP获得的性能优势产生重大影响。
请注意,如果您的项目使用%(RelativeDir)用于对象文件,则该配置很可能会尝试避免不同文件夹中具有相同名称的cpp文件冲突。
/Fo命令行参数通常是编译器转储obj文件的文件夹 - 只传递了一个文件夹,因此给定目录中的所有cpp文件只能一次传递给CL.exe。
那真是件痛苦的事情 - 但我很高兴有一个原因和解决方案。希望有所帮助。
更新
一个团队成员发现,每当将MSBuild参数发送到CL.exe时,似乎会破坏或严重限制/MP。这很可能是因为为了使/MP正常工作,顶级CL.exe需要有一组cpp文件。
我们的解决方案是不使用任何msbuild参数(我认为是%params%)来进行'Object File Name'。这要求我们重命名一些cpp文件,以便它们不会冲突。
希望这在VS2012或VS2013中得到改变。

有趣的是,我遇到了一个自动生成的项目文件相同的问题,对于所有具有相对于 $(IntDir) 的 ObjectFileName 设置的文件也都按顺序编译。它们都在单独的文件夹中,而且总是有几个同名的(因此在 XML 中进行了 ObjectFileName 指定)。看起来没有容易的解决办法,即使对象文件名不重叠... - OregonGhost
我有完全相同的问题。我们将源文件重组为子文件夹,以获得清晰的文件夹层次结构。现在构建时间慢了3倍。是否有任何选项可以解决这个问题? - Maik

1

我在这里提出了一种新的方法来避免 .obj 冲突问题

https://dev59.com/tW865IYBdhLWcg3wnP2a#26935613

考虑到上述情况,我可以详细阐述“每当将MSBuild参数发送到CL.exe时,似乎都会破坏或严重限制/MP”的问题。 /MP每次仅在一个CL.exe调用中起作用。 当你“发送一个msbuild参数”时,实际上是创建多个CL.exe调用,每个源文件都有特定的命令行。 这些无法批处理。 上面链接的解决方案尝试通过为最小化的输出目录定制命令行来解决这个问题。

这应该可以解决您的问题,同时避免破坏并保持高度批处理,只要您的项目不包含100个不同目录中名为x.cpp的文件...通常在我的经验中,在一个更大的项目中只有几个碰撞.obj文件。


1

根据MSDN的说法,文件应该在有线程处理它们时编译,但同时也不能保证文件编译的顺序:

源文件可能不会按照命令行上出现的顺序进行编译。虽然编译器创建了一组包含编译器副本的进程,但操作系统安排每个进程执行的时间。因此,您无法保证源文件将按特定顺序编译。

当有进程可用于编译源文件时,源文件将被编译。如果文件数多于进程数,则第一批文件将由可用进程编译。当进程完成处理前一个文件并可用于处理其余文件时,将处理其余文件。

它还指出,创建的进程数量将受到命令行中线程和文件数量的限制:

该值是您在命令行上指定的源文件数和线程数中较小的那个值

结合这两个方面,我们可以看到编译器逐步(按文件)处理编译,以便能够正确地将工作分配给子进程,它通过文件夹来实现。

如果您生成了自定义的 make 文件,则可能可以绕过此问题,在其中应该能够同时处理多个文件夹(或尝试使用 MSBUILD.exe 工具)。


我得出结论,这确实是msbuild如何将cpp文件分组并缓慢地分配给编译器的方式。 - Zac

0

确认了这个问题,而且原因不容易找到,所以让我们为其他遇到此问题的用户添加更多关键词。

我注意到我的最新 MSVC 项目重建时间太长了。特别是,CPU 核心几乎没有被利用,任务管理器中的 cl.exe 进程数量变化很大,输出窗口显示源文件被编译成批处理。每次编译一到十六个文件,稍作停顿,然后再编译一组文件,如此往复。相比之下,在我的旧项目中,CPU 几乎完全被使用,并且输出窗口显示源文件正在连续编译。

现在,我的新项目的一个重要区别是更好地使用命名空间和匹配的目录结构,这意味着一些类具有相同的名称并且由于不同的 .obj 文件指向同一个目录而导致冲突,因此需要在 C/C++ -> 输出文件中更改对象文件名。

输出窗口的更新也与目录结构匹配。如果有一个包含一个源文件的命名空间/目录,则 VS 仅显示一个文件被编译。下一个目录有 10 个源文件,VS 显示同时编译所有 10 个文件。

并没有太多的解决方案。要么避免使用相同名称的类并且不更改对象文件名,要么使用 zeromus发布的解决方法,它的功能非常好。我的重建时间从03:15缩短到了01:20,这是相当大的差异,并且在大部分编译期间CPU利用率从约35%增加到了100%。

VS 2015、2017和2019都以这种方式运行,所以改变的希望不大。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接