21

我有一个 SCons 脚本,它需要大约 10 秒才能发现不需要重建任何东西,这对于本质上是一个相当小的项目来说感觉非常长。阅读 SConscript 本身只需要一两秒钟,大部分时间花在:

scons: Building targets ...

步。

我怎样才能知道此时到底scons在做什么?对于编写快速 SCons 脚本,还有哪些其他一般性建议?

4

7 回答 7

25

(直接盗用http://www.scons.org/wiki/GoFastButton

命令 'scons --max-drift=1 --implicit-deps-unchanged' 将尽可能快地执行您的构建。

或者:

  • env.Decider('MD5-timestamp'):从 SCons 0.98 开始,您可以在环境中设置 Decider 函数。MD5-timestamp 表示如果时间戳匹配,不要费心重新 MD5ing 文件。这可以带来巨大的加速。有关信息,请参见手册页。
  • --max-drift:默认情况下,SCons 将在每次运行时计算构建中每个源文件的 MD5 校验和,并且仅在文件 2 天后缓存校验和。此默认值为 2 天是为了防止来自 NFS 或修订控制系统的时钟偏差。您可以使用 --max-drift=SECONDS 调整此延迟,其中 SECONDS 是一些秒数。减少 SECONDS 可以通过消除多余的 MD5 校验和计算来提高构建速度。您可以使用“SetOption('max_drift', SECONDS)”在 SConstruct 或 SConscript 文件中设置此选项,而不是在每次运行时在命令行中指定此选项。
  • --implicit-deps-unchanged:默认情况下,SCons 将重新扫描所有源文件以查找隐式依赖项(例如 C/C++ 标头#includes),这可能是一个昂贵的过程。您可以使用 --implicit-deps-unchanged 告诉 SCons 缓存调用之间的隐式依赖关系。通过使用此选项,您向 SCons 承诺自上次运行以来您没有更改任何隐式依赖项。如果您确实更改了隐式依赖项,那么使用 --implicit-deps-changed 将导致它们被重新扫描和缓存。(您不能在 SConstruct 或 SConscript 文件中设置此选项。)
  • --implicit-cache:此选项告诉 SCons 智能缓存隐式依赖项。它尝试确定自上次构建以来隐式依赖项是否发生了变化,如果是,它将重新计算它们。这通常比使用 --implicit-deps-unchanged 慢,但也更准确。您可以使用“SetOption('implicit_cache', 1)”在 SConstruct 或 SConscript 文件中设置此选项,而不是在每次运行的命令行上指定此选项。
  • CPPPATH:通常你通过设置 CPPPATH 构造变量告诉 Scons 包含目录,这会导致 SCons 在执行隐式依赖扫描时搜索这些目录,并在编译命令行中包含这些目录。如果您有从不或很少更改的头文件(例如系统头文件或 C 运行时头文件),那么您可以将它们从 CPPPATH 中排除,并将它们包含在 CCFLAGS 构造变量中,这会导致 SCons 在扫描时忽略这些包含目录对于隐式依赖。以这种方式仔细调整包含目录通常可以显着提高速度,而准确性损失很小。
  • 通过使用 env.SourceCode(".", None) 避免 RCS 和 SCCS 扫描 - 如果您在程序中使用大量 c 或 c++ 标头并且您的文件系统是远程的(nfs、samba),这将特别有趣。
  • 使用“BuildDir”时,将“duplicate”设置为 0:“BuildDir( dir1, dir2, duplicate=0”。这将导致 scons 使用 src_dir 中源文件的路径名和派生的路径名调用 Builders build_dir 中的文件。但是,如果在构建期间生成源文件,如果任何调用的工具被硬编码以将派生文件放在与源文件相同的目录中,这可能会导致构建问题。
  • 在多处理器机器上,一次运行多个作业可能是有益的 - 使用 --jobs N(其中 N 是机器上的处理器数量)或 SConstruct 中的“SetOption('num_jobs', N)”或 SConscript。在 Windows 机器上,处理器的数量在环境变量“NUMBER_OF_PROCESSORS”中可用。
  • 如果您有几十个预处理器定义(“-DFOO1 -DFOO2”),您可能会发现使用 --profile 时 SCons 在 subst() 函数中花费了大量时间,通常只是将 -D 字符串添加到定义中一遍又一遍地。这确实会减慢没有任何变化的构建。通过 100 多个定义,我看到使用本页其他地方的“缓存 CPPDEFINES”中描述的想法,无操作构建时间从 35 秒下降到 20 秒。

使事情变得更快的另一个技巧是避免在共享库已修改但未重建时重新链接程序。请参阅SharedLibrarySignatureOverride

于 2009-09-22T18:14:32.450 回答
9

scons md5-sums 文件以找出它们已更改,因此它几乎 md5sums 您的所有文件。

您可以告诉它只使用时间戳来决定要重建什么,而不必每次都对所有文件进行 MD5sum ,就像'make'所做的那样,这应该会加快速度。它可能更脆弱。例如,如果文件在上次构建后的 1 秒内发生了变化,scons 不会注意到这一点。采用

env.Decider('timestamp-newer')

还有 MD5-timestamp,它会先检查时间戳,如果时间戳较新,则使用 Md5 比较内容是否实际更改。

env.Decider('MD5-timestamp')

另一个加快速度的简单选择是使用 -j 参数运行并行构建。

scons -j 2

在我的 2 核机器上,-j 3 通常会提供最大的加速。

可以使用调用 scons 的 --debug 参数来完成关于 scons 正在做什么的一些输出,请参阅手册页以了解各种选项。

于 2009-08-23T16:13:04.693 回答
8

做了一些试验和错误来弄清楚为什么 SCons 很慢,到目前为止的一些发现(确切的结果当然会根据 SCons 脚本的结构和复杂性而有所不同):

  • CacheDir()没有明显的负面影响。
  • Decider()只有很小的影响,不值得打扰。
  • 使用variant_dir/VariantDir()将构建时间增加约 10%。
  • 读取SConstruct文件本身大约需要完成 scons 调用的 10%。
  • 迄今为止最大的影响似乎是库依赖项,在项目中使用 Gtkmm 使我的构建时间增加了一倍。

可能的解决方案:

  • 不要进行完全重建,而只重建您正在处理的目录/模块(scons -u而不是scons -D)。
  • 制作SConscript可选的部分,因此它仅在手动调用时重建。
  • -isystem使用库包含的编译器标志而不是-I,仅此更改就将构建时间从 10.5 秒缩短到 6 秒,只需调用 sed 即可轻松完成:

    env.ParseConfig('pkg-config --cflags --libs gtkmm-2.4 | sed "s/-I/-isystem/g"')

    不完全确定为什么会这样,我假设它减少了gcc输出的依赖关系,从而减少了scons跟踪的依赖关系。

当然也强烈推荐使用CacheDir()and scons -j N,但只会加速实际构建,而不是对 SCons 脚本本身的评估。

于 2009-08-24T16:11:35.967 回答
7

将第 3 方包括移出 CPPPATH 并移入 CCFLAGS 产生了巨大的变化。对于我们有 12 个外部包含目录(包括 boost 和 python)的项目,无操作编译从 30 秒缩短到 3 秒——加速了 10 倍。

于 2014-09-30T11:50:43.487 回答
5

当 SCons 首次创建时Environment,它会进行一系列查找以查看可用的工具。您可以通过DefaultEnvironment在创建第一个env.

DefaultEnvironment(tools=[])
于 2014-05-02T12:10:06.557 回答
2

添加到您的 SConscript 类似

if 'explain' in GetOption("debug"):
    Progress('Evaluating $TARGET\n')

并运行--debug=explain. 您将看到 SCons 花费时间评估的内容

于 2017-04-05T00:13:11.420 回答
1

scons --profile+snakeviz

这种组合向我展示了瓶颈到底是什么。

--profilecProfile以格式输出二进制文件,该文件存在于 stdlib 中

snakeviz然后是一个很棒的可视化工具,可以在 GUI 中快速查看该文件:

scons --profile f.prof
pip install -u snakeviz
snakeviz f.prof

输出如下所示:

在此处输入图像描述

您可以将鼠标悬停在每个框上以查看包含该函数的文件的完整路径。

更一般的 Python 上下文中的问题:是否有任何简单的方法来对 python 脚本进行基准测试?

--debug+ts -s

这并没有解决我的具体问题,但它通常可以给你一些想法:

time scons --debug=count,duplicate,explain,findlibs,includes,memoizer,memory,objects,prepare,presub,stacktrace,time |
  ts -s | tee f

示例输出摘录显示了我在 2 到 10 秒之间的巨大时间间隔,这是我试图集中注意力的地方:

00:00:02 SConscript:/data/gem5/master3/build/ARM/sim/power/SConscript  took 1.556 ms                                       
00:00:02 dup: relinking variant 'build/ARM/sim/probe/SConscript' from 'src/sim/probe/SConscript'                              
00:00:02 Building build/ARM/sim/probe/SConscript with action:                                                                                                                
00:00:02   UnlinkFunc(target, source, env)                                                                                      
00:00:02 Building build/ARM/sim/probe/SConscript with action:                                                                  
00:00:02   LinkFunc(target, source, env)                                                                                                            
00:00:02 SConscript:/data/gem5/master3/build/ARM/sim/probe/SConscript  took 0.401 ms       
00:00:10 SConscript:/data/gem5/master3/build/ARM/tests/opt/SConscript  took 98.225 ms                                               
00:00:10 SConscript:/data/gem5/master3/build/ARM/SConscript  took 8885.387 ms            
00:00:10 SConscript:/data/gem5/master3/SConstruct  took 9409.641 ms                         
00:00:10 scons: done reading SConscript files.                                                                                    
00:00:10 scons: Building targets ...

在 scons 3.0.1、Ubuntu 18.04 中测试。

也可以看看

于 2020-01-16T17:17:15.773 回答