【问题标题】:How to optimize the startup time of an SCons script?如何优化 SCons 脚本的启动时间?
【发布时间】:2010-11-22 01:50:09
【问题描述】:

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

scons: Building targets ...

一步。

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

【问题讨论】:

    标签: build-process build-automation scons


    【解决方案1】:

    (直接从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 是计算机上的处理器数)或“SetOption('num_jobs', N)”在您的 SConstruct 或 SConscript 中。在 Windows 机器上,处理器的数量在环境变量“NUMBER_OF_PROCESSORS”中可用。
    • 如果您有几十个预处理器定义(“-DFOO1 -DFOO2”),您可能会发现使用 --profile 时 SCons 在 subst() 函数中花费了大量时间,通常只是添加 -D 字符串一遍又一遍地定义。这确实会减慢没有任何变化的构建。通过 100 多个定义,我看到使用本页其他地方的“缓存 CPPDEFINES”中描述的想法,无操作构建时间从 35 秒下降到 20 秒。

    另一个加快速度的技巧是避免在共享库已修改但未重建时重新链接程序。见SharedLibrarySignatureOverride

    【讨论】:

      【解决方案2】:

      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 正在做什么的一些输出可以通过调用 scons 的 --debug 参数来完成,有关各种选项,请参见手册页。

      【讨论】:

      • 我玩过 Decider() 但它对构建时间的影响几乎为零(即在 0.1 秒范围内)。我也已经是“scons -j 2”了,但这仅有助于构建本身,而不是处理依赖项,该部分似乎只使用一个核心。
      • 我需要在每个 SConscript 文件中,还是在初始 SConstruct 文件中设置 Decider,才能在整个构建中使用它?
      • 顶部的 SConstruct 文件就足够了。 (尽管如果您以某种方式在其他地方创建了未从您调用 Decider() 的环境复制/克隆的单独环境,则需要为该环境再次调用它。 - 如果您没有实例化任何环境,只需调用Decider() 而不是 env.Decider() )
      【解决方案3】:

      进行了一些试验和错误以找出 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()scons -j N,但只会加速实际构建,而不是对 SCons 脚本本身的评估。

      【讨论】:

      • SCons 不使用 GCC 的依赖检查器,它有自己的基于 Python 的正则表达式集来查找包含。使用 -isystem 更快,仅仅是因为您对 SCons 隐藏了所有这些标头。如果这些标题中的任何一个发生了变化,SCons 无法判断。您所做的实际上是剔除由隐式依赖项(标头)表示的一大块 DAG。这会以正确性为代价加快遍历步骤。
      【解决方案4】:

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

      【讨论】:

      • 对我来说同样的经历。在 CPPPATH 中拥有 Boost 路径只会因许多因素而炸毁依赖关系树。有一些关于 boost 库的东西会导致它。可能是因为 .hpp 文件有很多包含。
      【解决方案5】:

      当 SCons 创建第一个 Environment 时,它会进行一系列查找以查看可用的工具。在创建您的第一个 env 之前,您可以通过在 DefaultEnvironment 中明确选择工具来避免不必要的检查和加速 SCons。

      DefaultEnvironment(tools=[])
      

      【讨论】:

        【解决方案6】:

        添加到你的 SConscript 类似

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

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

        【讨论】:

          【解决方案7】:

          scons --profile + snakeviz

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

          --profile输出cProfile格式的二进制文件,即present in the stdlib

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

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

          输出如下所示:

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

          更一般的 Python 上下文中的问题:Is there any simple way to benchmark python script?

          --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 中测试。

          另请参阅

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-10-20
            • 2021-08-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-05-14
            • 2011-03-12
            相关资源
            最近更新 更多