【发布时间】:2016-02-16 15:47:54
【问题描述】:
我对 CMake 的生成文件阶段很慢有一个问题,这类似于这个未回答的问题:
CMake is slow to generate makefiles
我的项目由顶级CMakeLists.txt 文件组成,该文件使用add_subdirectory() 为各个库和可执行组件添加各种子项目。
对于给定的组件,CMakeLists.txt 文件包含如下内容:
add_library(mylib SHARED
sourceFile1.cpp
sourceFile2.cpp
...
)
我可以使用以下方法仅构建该目录的内容:
make mylib
如果我修改子目录中的 CMakeLists.txt 文件(作为从纯 Makefile 迁移到 CMake 的一部分,我已经做了很多工作)然后运行 make 它正确地重新运行 CMake 以更新配置就像我运行make rebuild_cache。
但是,我注意到它实际上重新配置了整个项目。我真的希望 CMake 足够聪明,知道它只需要在当前目录和子目录中重新生成 Makefile。
有没有更好的方法来构建 CMake 项目来实现这一点? 我看到有些人对每个子项目中的每个 CMakeLists.txt 都使用 project()。总的来说,这是个好主意吗?
或者/另外有什么方法可以加快 CMake 的生成步骤?(目前我有 60 多岁)
如果您想讨论为什么 CMake 本身应该或不应该能够并行运行,则可以加分(想象一下 cmake -j)。
我添加了 meson-build 标签作为适度的赏金,但仅此一项还没有引起足够的关注来保证答案。正是这种问题可能会导致人们将构建系统切换到 meson-build(假设它没有类似问题)或类似的东西。
正确的答案可能是如果不将源代码修改为 CMake 就无法完成。为了获得赏金,我需要解释 CMake 的工作原理和/或存在缺陷的地方。
澄清:在我的情况下,生成步骤很慢。配置本身足够快,但 CMake 在输出 "-- 配置完成" 和 "-- 生成完成" 之间会挂起很长一段时间。
对于完整的缓存重建,我运行:
make -n rebuild_cache
运行 CMake 以重新生成构建系统...
使用 Makefile 生成器
-- FOOBAR_VERSION: 01.02.03
-- 配置完成
-- 生成完成
-- 构建文件已写入:/home/brucea/work/depot/emma/main/cmake
实际 74.87
用户 1.74
系统 1.02
在后台运行:
cmake -H<source dir> -B<build dir>
我认为-B 是--build 的同义词。文档中没有正确描述这两个选项。 -H 是源目录的根目录(与文档中的--help 不同)。
"Configuring done" 的输出很快,但从那里开始很慢:
例如,
15:44:14 execve("/usr/local/bin/cmake", >grep 生成 cmake_strace.log >grep “配置” cmake_strace.log 15:44:15 write(1, "-- 配置完成\n", 20-- 配置完成 15:45:01 write(1, "-- 生成完成\n", 19-- 生成完成 >grep “构建文件” cmake_strace.log 15:45:22 write(1, "-- 构建文件已被写入"..., 77-- 构建文件已写入:如果在子目录中编辑单个 CMakeLists.txt 文件,并且
然后运行make -n,它运行:
cd /home/project/cmake && /usr/local/bin/cmake -H/home/project/cmake -B/home/project/cmake --check-build-system CMakeFiles/Makefile.cmake 0
--check-build-system 是另一个未记录的选项。
效果是一样的——重新生成整个构建系统,而不仅仅是当前的子树。 源内构建和源外构建之间的行为没有区别。
如果我运行跟踪,例如:
strace -r cmake --trace -H/home/project/cmake -B/home/project/cmake 2>&1 | tee cmake_rebuild_cache.log
sort -r cmake_rebuild_cache.log | uniq
花费的大部分时间似乎都花在(或之间)打开、访问和取消链接调用上。
每个任务的长度变化很大,但数量庞大。 我不知道 Labels.json 和 Labels.txt 文件是关于什么的(CMake 内部的东西)。
一次运行:
49.363537 打开(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testViewingSource1.dir/build.make”,O_RDONLY)= 5 1.324777 访问(“/home/projectbar/main/test/performance/CMakeFiles/performancetest.chvcthulhu.testChvcthulhuPerformance2.dir”,R_OK)= 0 0.907807 访问(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testPeripheralConnection2.dir”,R_OK)= 0 0.670272 unlink("/home/projectbar/main/src/foo2bar/Foo2Bar/CMakeFiles/foo2bar_lib.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.600272 访问(“/home/projectbar/main/test/foo2bar/testFilesModel2.ok”,R_OK)= 0 0.599010 访问(“/home/projectbar/main/test/hve2snafu/testInvalidByte2c.ok”,R_OK)= 0 0.582466 读取(5,“openjdk 版本 \”1.8.0_71\“\nOpenJ”...,1024)= 130 0.570540 writev(3, [{"# CMAKE 生成的文件: DO NOT E"..., 8190}, {"M", 1}], 2) = 8191 0.553576 关闭(4)= 0 0.448811 unlink("/home/projectbar/main/test/snafu2hve/CMakeFiles/test2.snafu2hve.testNoProbes2.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.431559 访问(“/home/projectbar/main/src/foo2bar/Foo2Bar/CMakeFiles/foo2bar_lib.dir”,R_OK)= 0 0.408003 unlink("/home/projectbar/main/test/lachesis/CMakeFiles/test2.lachesis.testBadSequenceNumber1.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.407120 write(4, "# 语言集"..., 566) = 566 0.406674 write(3, "# CMAKE 生成的文件:不要 E"..., 675) = 675 0.383892 读取(3,“ewingPeriod.cpp.o -c /home/bruce”...,8191)= 8191 0.358490 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.chvdiff.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录)同一命令的另一次运行:
2.009451 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.lachesis.dir/Labels.json") = -1 ENOENT (没有这样的文件或目录) ) = 20 ) = 19 1.300387 访问(“/home/projectbar/main/test/chvedit/CMakeFiles/test2.chvedit.tefooultiMatchFactoringEdit2.dir”,R_OK)= 0 1.067957 访问(“/home/projectbar/main/test/chvedit/CMakeFiles/test2.chvedit.tefooultiMatchFactoringEdit2.dir”,R_OK)= 0 ) = 1 0.885854 unlink("/home/projectbar/main/src/gorkyorks2bar/CMakeFiles/doxygen.correct.gorkyorks2bar.dir/Labels.json") = -1 ENOENT(没有这样的文件或目录) 0.854539 访问(“/home/projectbar/main/test/reportImpressions/ReportImpressions/CMakeFiles/testsuite1_reportImpressions.dir”,R_OK)= 0 0.791741 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.bar_models.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.659506 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.647838 unlink("/home/projectbar/main/test/libyar/YarModels/CMakeFiles/testsuite1_yarmodels.dir/Labels.txt") = -1 ENOENT (没有这样的文件或目录) 0.620511 unlink("/home/projectbar/main/test/libyar/YarModels/CMakeFiles/testsuite1_yarmodels.dir/Labels.json") = -1 ENOENT (没有这样的文件或目录) 0.601942 unlink("/home/projectbar/main/cmake/CMakeFiles/mklinks.lachesis.dir/Labels.txt") = -1 ENOENT (没有这样的文件或目录) 0.591871 访问(“/home/projectbar/main/src/runbardemo/simple_demo/CMakeFiles”,R_OK)= 0 0.582448 写入(3,“CMAKE_PROGRESS_1 = \n\n”,21)= 21 0.536947 写(3,“CMAKE_PROGRESS_1 = \n\n”,21)= 21 0.499758 unlink("/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2bar.testInputDirectory1.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.458120 unlink("/home/projectbar/main/test/yak2dcs/CMakeFiles/test2.yak2dcs.testsuite2.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.448104 unlink("/home/projectbar/main/test/reportImpressions/CMakeFiles/test2.reportImpressions.dir/progress.make.tmp") = -1 ENOENT(没有这样的文件或目录) 0.444344 访问(“/home/projectbar/main/src/bananas/CMakeFiles/bin.bananas.dir”,R_OK)= 0 0.442685 unlink("/home/projectbar/main/test/rvedit/CMakeFiles/test2.rvedit.tefooissingOptionValue.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.425604 unlink("/home/projectbar/main/test/listdcs/CMakeFiles/test2.listdcs.testListCalls5.dir/progress.make.tmp") = -1 ENOENT (没有这样的文件或目录) 0.391163 访问(“/home/projectbar/main/src/siedit/CMakeFiles/siedit.dir”,R_OK)= 0 0.362171 访问(“/home/projectbar/main/test/foo2bar/CMakeFiles/test2.foo2emma.testHowResults6.dir”,R_OK)= 0请注意,Ninja 生成器要快得多(尽管仍然不够出色)。 例如,
/usr/bin/time -p ninja rebuild_cache
忍者:警告:多个规则生成 ../src/ams2yar/ams2yar。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
忍者:警告:多个规则生成 ../src/vox/vox。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/bananas/bananas。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
忍者:警告:多个规则生成 ../src/fidlertypes2fidlerinfo/fidlertypes2fidlerinfo。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/mkrundir/mkrundir。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/runyar/runyar。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
ninja:警告:多个规则生成 ../src/runyardemo/runyardemo。涉及此目标的构建将不正确;继续 [-w dupbuild=warn]
[1/1] 运行 CMake 重新生成构建系统...
发电机=忍者
-- FOO_VERSION: 01.02.03
-- 配置完成
-- 生成完成
-- 构建文件已写入:/home/project/cmake/build
真正的 12.67
用户 1.01
系统 0.31
请注意,该项目还没有为 Ninja 做好准备,因为存在如下错误:
ninja: warning: multiple rules generate ../src/runfoobardemo/runfoobardemo. builds involving this target will not be correct; continuing anyway [-w dupbuild=warn]
和
ninja: error: dependency cycle: ../src/foobar -> ../src/foobar/CMakeFiles/foobar -> ../src/ams2emma/foobar
待解决。这个问题实际上是关于为什么 Makefile 生成器很慢。我不确定 Ninja 显示的问题是这里有用的提示还是红鲱鱼。
构建具有更多优化的 CMake 并没有帮助。
根据我的跟踪输出和时间输出,不太可能。 用户时间以及因此在 CMake 代码本身中花费的时间非常少。 (参见例如 What do 'real', 'user' and 'sys' mean in the output of time(1)?)。
这是我为了完整性而尝试的:
export CXX_FLAGS="-O3 -ftree-vectorise -msse2"
cmake -DCMAKE_BUILD_TYPE=RELEASE
实际上使用更优化的 CMake确实 使配置部分更快, 但在我的情况下,generate 部分很慢。 从时间上看,这一步似乎是 I/O 绑定的。
我决定研究 Florian 的想法,即在文件流中使用内存来存储临时文件可能会有所作为。
我决定尝试简单的方法并破解 CMake 以将 .tmp 文件写入 RAM 磁盘。
然后我全力以赴并尝试在 RAM 磁盘上生成构建系统:
sudo mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=512m tmpfs /mnt/ramdisk
/usr/bin/time -p cmake -H/<source> -B/mnt/ramdisk/build
我很惊讶地发现这对挂钟时间没有任何影响:
real 59.61
user 1.55
sys 0.62
>du -sh /mnt/ramdisk/build/
4.4M /mnt/ramdisk/build/
与 ramfs 类似:
real 51.09
user 1.58
sys 0.50
这里会发生什么?我在猜测子流程,但我无法确定哪些子流程正在消耗挂钟时间(如果有的话)。他们看起来很短暂。
为了完整起见,以下是 perf 的一些输出(使用 -fno-omit-frame-pointer 构建的 CMake):
perf record -g --call-graph dwarf cmake -H<source> -B<build>
perf report -g graph
样本:17K 的事件“周期”,事件计数(大约):14363392067
儿童自我命令共享对象符号
+ 65.23% 0.00% cmake cmake [.] do_cmake
+ 65.02% 0.00% cmake cmake [.] cmake::Run
+ 60.32% 0.00% cmake cmake [.] main
+ 59.82% 0.00% cmake libc-2.17.so [.] __libc_start_main
+ 57.78% 0.00% cmake cmake [.] _start
+ 55.04% 0.00% cmake cmake [.] cmGlobalUnixMakefileGenerator3::Generate
+ 54.56% 0.00% cmake cmake [.] cmake::Generate
+ 49.90% 0.00% cmake cmake [.] cmGlobalGenerator::Generate
+ 38.87% 0.02% cmake cmake [.] cmLocalUnixMakefileGenerator3::Generate
+ 18.65% 0.01% cmake cmake [.] cmMakefileTargetGenerator::WriteTargetBuildRules
+ 17.05% 0.02% cmake cmake [.] cmMakefile::ExecuteCommand
+ 16.99% 0.01% cmake cmake [.] cmMakefile::ReadListFile
+ 16.84% 0.01% cmake cmake [.] cmCommand::InvokeInitialPass
+ 16.79% 0.00% cmake cmake [.] cmMakefile::Configure
+ 14.71% 0.00% cmake cmake [.] cmMakefile::ConfigureSubDirectory
+ 14.67% 0.05% cmake cmake [.] cmMacroHelperCommand::InvokeInitialPass
+ 14.27% 0.02% cmake cmake [.] cmMakefileUtilityTargetGenerator::WriteRuleFiles
+ 13.91% 0.00% cmake cmake [.] cmGlobalGenerator::Configure
+ 13.50% 0.05% cmake cmake [.] cmOutputConverter::Convert
+ 13.48% 0.00% cmake cmake [.] cmAddSubDirectoryCommand::InitialPass
+ 13.46% 0.00% cmake cmake [.] cmMakefile::AddSubDirectory
+ 12.91% 0.00% cmake cmake [.] cmGlobalUnixMakefileGenerator3::Configure
+ 12.82% 0.00% cmake cmake [.] cmake::ActualConfigure
+ 10.90% 0.00% cmake cmake [.] cmake::Configure
+ 10.55% 0.02% cmake cmake [.] cmMakefileTargetGenerator::WriteObjectRuleFiles
+ 10.35% 0.09% cmake cmake [.] cmLocalUnixMakefileGenerator3::WriteMakeRule
+ 9.76% 0.03% cmake cmake [.] cmMakefileTargetGenerator::WriteObjectBuildFile
+ 7.97% 0.00% cmake cmake [.] cmMakefileLibraryTargetGenerator::WriteRuleFiles
+ 7.93% 0.00% cmake cmake [.] cmMakefileExecutableTargetGenerator::WriteRuleFiles
+ 7.88% 0.00% cmake cmake [.] cmLocalUnixMakefileGenerator3::WriteLocalMakefile
+ 7.68% 0.02% cmake [kernel.kallsyms] [k] sysret_audit
+ 7.60% 0.05% cmake [kernel.kallsyms] [k] __audit_syscall_exit
+ 7.40% 0.08% cmake cmake [.] cmsys::SystemTools::CollapseFullPath
而性能报告 -g graph -no-children:
+ 2.86% cmake libc-2.17.so [.] _int_malloc + 2.15% cmake libc-2.17.so [.] __memcpy_ssse3_back + 2.11% cmake [kernel.kallsyms] [k] find_next_bit + 1.84% cmake libc-2.17.so [.] __memcmp_sse4_1 + 1.83% cmake libc-2.17.so [.] _int_free + 1.71% cmake libstdc++.so.6.0.20 [.] std::__ostream_insert > + 1.18% cmake libstdc++.so.6.0.20 [.] std::basic_string, std::allocator >::~basic_string + 1.13% cmake libc-2.17.so [.] malloc + 1.12% cmake cmake [.] cmOutputConverter::Shell__ArgumentNeedsQuotes + 1.11% cmake libstdc++.so.6.0.20 [.] std::string::compare + 1.08% cmake libc-2.17.so [.] __strlen_sse2_pminub + 1.05% cmake cmake [.] std::string::_S_construct + 1.04% cmake cmake [.] cmsys::SystemTools::ConvertToUnixSlashes + 0.97% cmake cmake [.] yy_get_previous_state + 0.87% cmake cmake [.] cmOutputConverter::Shell__GetArgument + 0.76% cmake libstdc++.so.6.0.20 [.] std::basic_filebuf >::xsputn + 0.75% cmake libstdc++.so.6.0.20 [.] std::string::size + 0.75% cmake cmake [.] cmOutputConverter::Shell__SkipMakeVariables + 0.74% cmake cmake [.] cmOutputConverter::Shell__CharNeedsQuotesOnUnix + 0.73% cmake [kernel.kallsyms] [k] mls_sid_to_context + 0.72% cmake libstdc++.so.6.0.20 [.] std::basic_string, std::allocator >::basic_string + 0.71% cmake cmake [.] cmOutputConverter::Shell__GetArgumentSize + 0.65% cmake libc-2.17.so [.] malloc_consolidate + 0.65% cmake [kernel.kallsyms] [k] mls_compute_context_len + 0.65% cmake cmake [.] cmOutputConverter::Shell__CharNeedsQuotes + 0.64% cmake cmake [.] cmSourceFileLocation::Matches + 0.58% cmake cmake [.] cmMakefile::ExpandVariablesInStringNew + 0.57% cmake cmake [.] std::__deque_buf_size + 0.56% cmake cmake [.] cmCommandArgument_yylex + 0.55% cmake cmake [.] std::vector >::size + 0.54% cmake cmake [.] cmsys::SystemTools::SplitPath + 0.51% cmake libstdc++.so.6.0.20 [.] std::basic_streambuf >::xsputn【问题讨论】:
-
project()既不修改解析流程也不修改 makefile 生成流程。它只是添加了几个 CMake 变量的定义。 -
我遇到了类似的问题,并且使用更多优化选项重建 CMake 本身,然后正态分布确实将我的配置时间从大约 20 秒减少到不到 1 秒。您使用哪个主机系统?另一种选择是使用
ExternalProject_Add()将一个大项目划分为子项目(参见例如我的答案here),但这会增加初始配置步骤的持续时间。最后,总是有二进制交付的可能性(预编译库)。 -
你看到CMake Performance Tips了吗?因为我认为这更多是配置步骤问题,然后是生成步骤问题(仅写入文件通常很快)。你能试试Investigation/profiling methods吗?仅仅是因为配置步骤的持续时间主要取决于您在
CMakeLists.txt文件中所做的工作。 -
我想过 externalProject_add() 但实际上子项目不是外部的。它们是同一源代码树的一部分。特别是它们共享相同的配置并且强耦合(如您的回答)。在这种情况下使用 ExternalProject_Add() 作为解决方法是否被认为是良好的“现代 cmake”实践?
-
请注意,我查看了性能提示并使用了 trace / strace。我什至稍微研究了 cmake 的来源。没有什么明显的跳出,我没有机会更深入地研究。
标签: build cmake build-process meson-build