如何在 CMake 3.7 中表达 PGO 依赖项?

Chr*_*vey 5 c++ cmake clang pgo

我有一个 C++ 程序,我正在使用 Clang 3.9 的配置文件引导优化功能构建它。这是应该发生的事情:

  1. 我在启用检测的情况下构建程序。
  2. 我运行那个程序,用 profile-data: 创建一个文件prof.raw
  3. llvm-profdata用来转换prof.raw为一个新文件,prof.data.
  4. 我创建了同一程序的新版本,并进行了一些更改:
    • 将每个 .cpp 文件编译为 .o 文件时,我使用编译器标志-fprofile-use=prof.data
    • 链接可执行文件时,我还指定了-fprofile-use.

为此,我有一个 Gnu Makefile,效果很好。我的问题出现了,因为我试图将该 Makefile 移植到 CMake(3.7,但我可以升级)。我需要使用(至少)Unix Makefiles 生成器的解决方案,但理想情况下它适用于所有生成器。

在 CMake 中,我定义了两个可执行目标:foo-genfoo-use

  • foo-gen被执行时,它创建的prof.raw文件。
  • add_custom_command用来创建一个规则来创建prof.dataprof.raw.

我的问题是我不知道如何告诉 CMake 依赖的每个目标文件foo-use都依赖于文件prof.data.

  • 我最有希望的想法是 (1) 找到一种方法来枚举所有.o依赖的文件foo-use,然后 (2) 遍历每个.o文件,调用add_dependency每个文件。

    这种方法的问题是我在我的 CMakeLists.txt 文件中找不到一种惯用的方法来枚举可执行文件所依赖的目标文件列表。这可能是 CMake 的一个悬而未决的问题

  • 我还考虑使用在我使用的每个文件上set_source_files_properties设置OBJECT_DEPENDS属性,添加到该属性的列表中。.cppfoo-useprof.data

    这个(AFAICT)的问题在于我的每个.cpp文件都用于创建两个不同的.o文件:foo-gen一个用于foo-use. 我希望.o链接到的文件在foo-use编译时依赖于prof.data; 但.o该获取链接到文件中foo-gen必须没有对编译时的依赖prof.data

    而且据我所知,set_source_files_properties并没有让我设置的OBJECT_DEPENDS属性对是否两个中的一个值,队伍foo-gen或者foo-use是感兴趣的当前目标。

有什么建议可以用一种干净的(ish)方式来完成这项工作吗?

sta*_*all 3

讨论作者的想法#1

我最有希望的想法是(1)找到一种方法来枚举所有.o依赖的文件foo-use,然后(2)迭代每个.o文件,调用add_dependency每个文件。

根据 的文档,这不应该起作用add_dependencies,其中指出:

使顶级目标依赖于其他顶级目标,以确保它们先于其他目标构建。

IE。您不能使用它来使目标依赖于文件- 只能依赖于其他目标

讨论作者的想法#2

我还考虑使用 set_source_files_properties 来设置所使用的OBJECT_DEPENDS每个文件的属性,并将其添加到该属性的列表中。.cppfoo-useprof.data

这个问题(AFAICT)是我的每个.cpp文件都用于创建两个不同的.o文件:一个用于foo-gen,一个用于foo-use. 我希望.o链接到的文件foo-use具有对 ; 的编译时依赖性prof.data。但.o链接到的文件foo-gen不得具有对prof.data.

而且据我所知,set_source_files_properties不允许我将OBJECT_DEPENDS属性设置为两个值之一,具体取决于是否foo-genfoo-use当前感兴趣的目标。

在评论部分,您提到如果OBJECT_DEPENDS支持生成器表达式,您可以解决这个问题,但事实并非如此。附带说明一下,CMake gitlab repo 上有一个跟踪此问题的问题单。您可以点赞并描述您的用例以供他们参考。

在评论部分您还提到了一个可能的解决方案:

潜在的其他解决方案 a) 双项目系统,其中主用户调用项目将设置转发到第二个 pgo 项目,再次编译相同的设置。

实际上,您可以通过以下方式将其放入 CMake 项目中,ExternalProject使其成为生成的构建系统的一部分:使顶级项目将其自身包含为外部项目。外部项目可以传递一个缓存变量来配置它的版本-gen,顶层可以是版本-use

从经验来看,如果您以前从未手动调用或做过任何事情ExternalProject,那么这完全是一个漫长的 CMake 文档阅读和修饰会话的另一个兔子洞,因此答案可能属于专门针对它的新问题。

这可以解决 中没有生成器表达式的问题OBJECT_DEPENDS,但是如果您想为顶级项目进行多重配置,并且多重配置中的某些配置不适用于 PGO,那么您将回到方一。

建议的解决方案

以下是我发现的在配置文件数据更改时使源代码重新编译的方法:

  1. 对于运行训练可执行文件并生成和重新格式化训练数据的自定义命令,添加另一个COMMAND生成在注释中包含时间戳的 C++ 头文件的命令。
  2. 如果重新运行训练,请将该标头包含在您想要重新编译的所有源中。

如果您想支持非 PGO 构建,请将时间戳标头包装在标头中,该标头会检查它是否存在,__has_include并且仅在存在时才包含它。

我非常确定,使用这种方法,CMake 不会对配置文件数据进行 TU 的依赖性检查,而是由生成的构建系统的标头依赖性跟踪来完成这项工作。在头文件中包含时间戳注释而不是仅仅“触摸”它来更改文件系统中的时间戳的基本原理是,生成的构建系统可能会通过文件内容而不是文件系统时间戳来检测更改。

所提出的解决方案的所有缺点

这种方法的明显弱点是您需要向所有要检查重新编译的 .cpp 文件添加头文件。由此可能产生几个问题(从最严重到最严重):

  1. 从美学的角度来看,你可能不喜欢它。

  2. 这肯定会导致人为错误,忘记包含新 .cpp 文件的标头。我不知道如何解决这个问题。有些编译器有一个标志,您可以使用它在每个源文件中包含一个文件,例如GCC 的-include标志MSVC 的/FI标志。然后,您可以使用以下命令将此标志添加到 CMake 目标target_compile_options(<target> PRIVATE "SHELL:-include <path>")

  3. 您可能无法更改需要重新编译的某些源,例如您的库所依赖的第三方静态库的源。ExternalProject如果您通过执行该步骤来使用,可能会有解决方法patch,但我不知道。

对于我的个人项目,#1 和#2 是可以接受的,而#3 恰好不是问题。如果您有兴趣,可以看看我是如何在那里做事的。

迈向标准 PGO CMake 模块

请参阅https://gitlab.kitware.com/cmake/cmake/-/issues/19273