ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bazel 内存优化完全指南:限制、回收与 Profiling 实战

Bazel 内存优化完全指南:限制、回收与 Profiling 实战 Bazel 内存优化完全指南限制、回收与 Profiling 实战【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel导读本文围绕 Bazel 官方文档 Optimize Memory 展开系统讲解三种控制 Bazel 内存占用与回收的策略——通过--host_jvm_args限制 JVM 堆上限、通过--discard_analysis_cache/--nokeep_state_after_build/--notrack_incremental_state以牺牲增量构建速度换取低内存、以及实验性的 Skyfocus 机制在保留增量速度的同时聚焦工作集以回收堆内存。同时介绍内置内存分析器Memory Profiler与bazel dump系列命令的完整排查流程。读完本文你将掌握应对大型仓库 OOMOutOfMemoryError与高内存占用的全套命令行方案并理解每个标志背后的源码级原理。一、概述Bazel 的内存问题从何而来Bazel 是一个运行在 JVM 之上的构建系统其分析analysis与执行execution阶段会在内存中维护大量状态Skyframe 增量依赖图、已加载的包package、已配置的目标configured target、动作缓存等。仓库越大、目标越多这些状态所占用的堆内存就越高。当堆内存不足时Bazel 会抛出OutOfMemoryErrorOOM导致构建失败。官方文档 Optimize Memory 给出了三条清晰的优化路径对应不同的取舍策略核心手段收益代价限制堆上限--host_jvm_args-Xmx…强制控制峰值内存堆不足时可能 OOM 或更频繁 GC放弃增量状态--discard_analysis_cache、--nokeep_state_after_build、--notrack_incremental_state显著降低构建期间内存后续增量构建变慢甚至需要全量重建聚焦工作集Skyfocus实验性--experimental_enable_skyfocus--experimental_active_directories既省内存又保留增量速度工作集之外的改动会被拒绝/报错下文逐一展开。二、限制 Bazel 使用的内存--host_jvm_args2.1 基础用法Bazel 的守护进程server运行在 JVM 中其堆大小由 startup flag--host_jvm_args控制。要限制最大堆直接传入 JVM 参数即可bazel build //pkg:target --host_jvm_args-Xmx2g上述命令将 Bazel server 的 JVM 最大堆限制为 2GB。-Xmx是标准 JVM 堆上限参数可配合单位k、m、g如-Xmx512m、-Xmx4g。从源码看该选项定义于 HostJvmStartupOptions.java并在startup_options.txt见 startup_options.txt中有对应文档条目属于 Bazel 启动startup选项因此必须在bazel命令与子命令之间、且在解析其他命令行参数之前生效。2.2 适用场景与注意事项适用于CI 容器、共享开发机等内存受限环境需要强制 Bazel 的峰值堆不超过某个阈值。注意--host_jvm_args是startup flag因此必须放在命令开头bazel --host_jvm_args-Xmx2g build …与bazel build --host_jvm_args-Xmx2g …的语义相同Bazel 会自动识别 startup flag 并前移。若要长期生效推荐写入.bazelrc的startup段# .bazelrc startup --host_jvm_args-Xmx2g局限仅限制 JVM 堆并不能主动降低内存占用当构建本身需要的内存超过上限时仍然会触发OutOfMemoryError。因此它更适合与第三节的放弃增量状态策略配合使用。三、牺牲增量构建速度换取内存如果构建规模过大导致 OOM可以组合使用三个命令标志让 Bazel 在构建过程中尽可能少地保留内存状态代价是后续增量构建速度下降bazel build //pkg:target \ --discard_analysis_cache \ --nokeep_state_after_build \ --notrack_incremental_state文档明确指出这三个标志组合使用可将单次构建的内存占用压到最低但会使后续构建比标准增量构建更慢。这三个标志也可以单独使用各自的效果不同。3.1--discard_analysis_cache分析缓存执行期更省内存减少的是执行阶段execution而非分析阶段的内存。增量构建时不需要重新做包加载package loading但需要重做分析与执行。好在磁盘上的 action cache 可以避免大部分动作的真正重执行。从实现上看--discard_analysis_cache的作用发生在分析完成后立即丢弃分析结果缓存从而在执行阶段释放大量堆内存。3.2--notrack_incremental_state不记录依赖边Bazel 内部维护一张增量依赖图Skyframe 图--notrack_incremental_state让 Bazel不存储任何边edges使该图对增量构建不可用。这些数据在下一次构建时才会被丢弃在此之前会保留用于内部调试——除非同时指定--nokeep_state_after_build。源码佐证该选项定义于 CommonCommandOptions.java默认值为true其effectTags为LOSES_INCREMENTAL_STATE丢失增量状态帮助信息中建议当设为false时通常还应该同时指定--batch。注意其旧名称是keep_incrementality_data。3.3--nokeep_state_after_build构建结束后立即清理构建结束后丢弃全部内存状态后续增量构建必须从头开始磁盘 action cache 除外。单独使用时它不影响当前构建的峰值内存high-water mark只影响构建结束后的内存驻留。源码佐证该选项独立定义于 KeepStateAfterBuildOption.java默认值同样为true。它与CommonCommandOptions分离是为了避免SkyframeExecutor引用CommonCommandOptions造成依赖环——这说明它直接参与 Skyframe 执行器的状态生命周期管理。3.4 三者差异速查标志何时释放内存增量影响磁盘 action cache 是否仍可用--discard_analysis_cache分析完成后、执行期间重新分析执行免重新加载包是--notrack_incremental_state数据保留到下次构建前图不可用于增量下次构建丢弃是--nokeep_state_after_build构建结束后下次构建全量重来是3.5 实操建议CI 一次性构建三个标志一起上把单次构建内存压到最低。本地反复迭代优先只加--discard_analysis_cache兼顾内存与部分增量能力。长期配置写入.bazelrc的build段即可全局生效# .bazelrc build --discard_analysis_cache build --nokeep_state_after_build build --notrack_incremental_state四、Skyfocus保留增量速度的内存回收方案实验性第三节的方案以牺牲增量速度换取内存。如果希望既降低内存、又保留增量构建速度Bazel 提供了实验性功能Skyfocus你告诉 Bazel 将要修改的工作集working set文件Bazel 就只为正确增量重建这些文件的改动而保留必要状态其余 Skyframe 状态全部回收。4.1 启用方式与默认工作集bazel build //pkg:target --experimental_enable_skyfocus默认情况下工作集 被构建目标旁边的一组文件。上例中//pkg下的所有文件都会保留在工作集内。工作集之外的任何文件改动都会被禁止构建报错直到你执行bazel clean或重启 Bazel server。4.2 指定精确工作集--experimental_active_directories如果默认工作集不符合需求可用--experimental_active_directories指定精确的文件/目录集合bazel build //pkg:target --experimental_enable_skyfocus \ --experimental_active_directoriespath/to/another/dir,path/to/tests/dir注意该标志接收以逗号分隔、相对于工作区根目录workspace root的路径列表。源码佐证这些选项全部定义于 SkyfocusOptions.javaexperimental_enable_skyfocus默认false总开关帮助文本明确指出启用后通过--experimental_active_directories降低增量构建的内存占用该功能即 Skyfocusexperimental_active_directories默认有状态标志stateful flag——一旦定义会在后续调用中持续生效直到重新定义新集合。这点对日常使用很重要指定一次后后续构建会沿用该工作集experimental_skyfocus_dump_keys默认none调试用可取值none/count/verbose分别用于输出被聚焦的 SkyKeysroots、leafs、focused deps、focused rdeps的数量统计或字符串表示experimental_skyfocus_dump_post_gc_stats默认false调试用启用后在聚焦前后手动触发 GC以报告堆内存缩减量会增加 Skyfocus 延迟。此外还有两个边界策略选项experimental_frontier_violation_check默认strict处理工作集之外发生改动的策略可取strict显式报错要求用户处理、warn尝试优雅处理并输出警告、disabled_for_testing仅测试用experimental_frontier_violation_verbose默认false为true时打印修复 Skycache 违规的指引。Skyfocus 的执行逻辑位于 SkyfocusExecutor.java由 SkyframeExecutor.java 调用是整个 Skyframe 状态管理的一部分。4.3 完整示例与内存收益度量组合使用上述标志并加上--experimental_skyfocus_dump_post_gc_stats显示内存回收量官方文档给出的完整输出如下$ bazel test //pkg:target //tests/... --experimental_enable_skyfocus --experimental_active_directoriesdir1,dir2,dir3/subdir --experimental_skyfocus_dump_post_gc_stats INFO: --experimental_enable_skyfocus is enabled. Blaze will reclaim memory not needed to build the working set. Run blaze dump --skyframeworking_set to show the working set, after this command. WARNING: Changes outside of the working set will cause a build error. INFO: Analyzed 149 targets (4533 packages loaded, 169438 targets configured). INFO: Found 25 targets and 124 test targets... INFO: Updated working set successfully. INFO: Focusing on 334 roots, 3 leafs... (use --experimental_skyfocus_dump_keys to show them) INFO: Heap: 1237MB - 676MB (-45.31%) INFO: Elapsed time: 192.670s ... INFO: Build completed successfully, 62303 total actions分析这个示例本次构建分析了 149 个目标、加载了 4533 个包、配置了 169438 个目标工作集更新成功聚焦了 334 个 roots 和 3 个 leafs可用--experimental_skyfocus_dump_keys查看具体键堆内存从 1237MB 降至 676MB回收 561MB约 45.31%后续对dir1、dir2、dir3/subdir下文件的增量构建保持原有速度代价是这些目录之外的文件一旦改动Bazel 无法增量重建默认strict模式下直接报错。4.4 查看工作集文档给出的示例输出中提示构建后可运行bazel dump --skyframeworking_setBazel 中对应blaze dump --skyframeworking_set来查看当前工作集内容便于确认 Bazel 实际聚焦了哪些路径。4.5 使用要点总结Skyfocus 是实验性功能行为可能随版本变化以当前仓库实现为准工作集之外的改动在strict默认策略下会直接导致构建错误需要bazel clean或重启 server 才能解除聚焦--experimental_active_directories是有状态标志设置后持续生效重新设置才会覆盖生产使用前建议先在分支/CI 上验证工作集划分是否合理避免频繁触碰工作集外文件。五、内存分析Memory Profiling定位规则级内存热点如果只是想限制或回收内存还不够你还需要知道内存到底被谁吃掉了。Bazel 内置内存分析器专门用于检查自定义规则的 Starlark 内存占用。本节完整整理官方自定义规则性能文档 Memory profiling 中的流程。5.1 启用内存跟踪必须的两个 startup flag要让 server 进入内存跟踪模式每次Bazel 调用都必须携带以下两个 startup flagsSTARTUP_FLAGS\ --host_jvm_args-javaagent:path to java-allocation-instrumenter-3.3.4.jar \ --host_jvm_args-DRULE_MEMORY_TRACKER1其中java-allocation-instrumenter-3.3.4.jar需要从 Maven Central 下载坐标为com.google.code.java-allocation-instrumenter:java-allocation-instrumenter:3.3.4并替换path to …为实际路径。⚠️ 关键约束只要有一次 Bazel 调用漏掉这两个 flagserver 就会重启并丢失跟踪状态必须重新开始。因此实践中应把STARTUP_FLAGS固化到.bazelrc的startup段或脚本中。从源码看RULE_MEMORY_TRACKER系统属性与内存跟踪模块相关联相关实现位于 AllocationTrackerModule.java即src/main/java/com/google/devtools/build/lib/profiler/memory/包内负责分配追踪与堆转储支撑。5.2 第一步只分析不构建以目标foo为例只运行分析阶段、跳过执行阶段加--nobuild$ bazel $(STARTUP_FLAGS) build --nobuild //foo:foo5.3 第二步查看整个 Bazel 实例的内存占用$ bazel $(STARTUP_FLAGS) info used-heap-size-after-gc 2594MBinfo used-heap-size-after-gc报告 GC 后的堆使用量是衡量 Bazel 进程整体驻留内存的快捷方式。5.4 第三步按规则类型分解内存$ bazel $(STARTUP_FLAGS) dump --rules RULE COUNT ACTIONS BYTES EACH genrule 33,762 33,801 291,538,824 8,635 config_setting 25,374 0 24,897,336 981 filegroup 25,369 25,369 97,496,272 3,843 cc_library 5,372 73,235 182,214,456 33,919 proto_library 4,140 110,409 186,776,864 45,115 android_library 2,621 36,921 218,504,848 83,366 java_library 2,371 12,459 38,841,000 16,381 _gen_source 719 2,157 9,195,312 12,789 _check_proto_library_deps 719 668 1,835,288 2,552 ... (more output)bazel dump --rules按规则类rule class输出实例数COUNT、动作数ACTIONS、总字节数BYTES与单实例平均字节EACH。上例中android_library单实例高达 83KB、proto_library单实例 45KB都是值得优先排查的对象。5.5 第四步导出 Starlark 堆转储并用 pprof 分析$ bazel $(STARTUP_FLAGS) dump --skylark_memory$HOME/prof.gz Dumping Starlark heap to: /usr/local/google/home/$USER/prof.gzdump --skylark_memoryfile导出 Starlark 堆的 pprof 格式文件。随后用 pprof 分析生成火焰图$ pprof -flame $HOME/prof.gz生成带行号的文本热点$ pprof -text -lines $HOME/prof.gz flat flat% sum% cum cum% 146.11MB 19.64% 19.64% 146.11MB 19.64% android_library native:-1 113.02MB 15.19% 34.83% 113.02MB 15.19% genrule native:-1 74.11MB 9.96% 44.80% 74.11MB 9.96% glob native:-1 55.98MB 7.53% 52.32% 55.98MB 7.53% filegroup native:-1 53.44MB 7.18% 59.51% 53.44MB 7.18% sh_test native:-1 26.55MB 3.57% 63.07% 26.55MB 3.57% _generate_foo_files /foo/tc/tc.bzl:491 26.01MB 3.50% 66.57% 26.01MB 3.50% _build_foo_impl /foo/build_test.bzl:78 22.01MB 2.96% 69.53% 22.01MB 2.96% _build_foo_impl /foo/build_test.bzl:73 ... (more output)-lines会把热点精确到.bzl文件的具体行号如_build_foo_impl /foo/build_test.bzl:78这正是定位规则内存泄漏/过度分配的最直接证据。说明pprof是独立的 Go 工具需另行安装例如通过go install或系统包管理器获取 google/pprof不随 Bazel 分发。5.6 内存排查完整流程小结步骤命令目的0两个 startup flags见 5.1开启内存跟踪模式1bazel build --nobuild //foo:foo只分析不执行2bazel info used-heap-size-after-gc看整体堆占用3bazel dump --rules按规则类型分解4bazel dump --skylark_memoryprof.gz导出 pprof 堆转储5pprof -flame/pprof -text -lines定位热点行六、综合决策建议场景推荐方案CI 单次构建、内存紧张--host_jvm_args-Xmx… 第三节三标志组合本地大仓增量迭代、内存吃紧--discard_analysis_cache单独使用需要同时保内存与增量速度尝试 Skyfocus --experimental_active_directories规则内存异常、需要定位5.1 的两个 startup flags bazel dump系列所有标志的最终权威定义均可在源码中核对CommonCommandOptions.java、KeepStateAfterBuildOption.java、SkyfocusOptions.java 与 HostJvmStartupOptions.java规则内存分析流程的完整版见 自定义规则性能文档。【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表