当前位置: 首页 > news >正文

Flutter 的 build_runner 已经今非昔比,看看 build_runner 2.13 有什么特别?

相信写过 Flutter 的都对build_runner不太感冒,主要是它在过去表现出来的性能太差,而且失败率又高,所以很多人对于build_runner的印象也一直停留在这里,能不用就不用。

但是随着这些年过去,其实build_runner已经变了不少,特别是从 v2.7.0 到 v2.13.0 ,通过加入了AOT 编译、大幅减少 I/O 与序列化开销、还有优化代码分析流程等支持,现在版本的性能已经提升了不少。

特别是 2.13.0 版本,在官方的测试里,它针对大型增量构建实现了最高 4 倍的加速,具体测试环境和指标为:

  • 测试环境:在单 CPU Linux 机器上运行,所有代码均经过AOT 编译(使用--force-aot),消除了 JIT 预热时间的影响
  • 项目规模:通过库(libraries)的数量(1000、5000、10000)来衡量项目的复杂程度
  • init(初始构建):从零开始的全量构建
  • incr(增量构建):在已有构建结果的基础上,修改少量代码后进行的再次构建。
  • web:同时应用built_value生成和 DDC(Dart Dev Compiler)编译的场景,更接近真实的 Web 开发流程

测试的结果可以看到,越大的项目提升越明显,从速度提升倍数可以看出:

  • 1000个库的小型项目中,初始构建提升约为1.4x
  • 10000个库的大型项目中,初始构建提升达到了3.0x,而增量构建更是达到了3.9x

另外,在所有规模下,增量构建(incr)的提升倍数普遍高过初始构建(init):

  • 10000 incr的平均耗时从 v2.12 的45.72 秒降到 v2.13.0 的11.80 秒

另外 “and analyzer” 也可以看出来,当build_runner配合下一代 Dart Analyzer 优化后的表现:

  • 在 10000 个库的增量构建中,速度提升将达到4.8x
  • 原本 45.72 秒的任务(v2.12),在双重优化下仅需9.46 秒即可完成

如果以 10000 库为基准,这个对比就很明显了:

版本阶段初始构建 (init)增量构建 (incr)Web 增量构建
v2.12 (旧版)48.75s45.72s55.24s
v2.13.0 (新版)16.12s (3.0x)11.80s (3.9x)18.11s (3.1x)
v2.13 + 新版分析器12.48s (3.9x)9.46s (4.8x)15.63s (3.5x)

而且这还是 2.13 对比 2.12 ,实际上从 2.10 开始本身就对比过去有不少提升

实际上,在 v2.10.0 - v2.12.0 也有不少性能提升的场景,例如:

  • AOT 编译构建器 (v2.10.0):
    • 开始加入--force-aot标志,以往 Builder 运行在 JIT 模式下,冷启动慢,而 AOT 模式下 Builder 启动更快且吞吐量更高
  • findAssets 扩展性优化 (v2.10.1):
    • 大幅提高了在拥有数千个文件的包中进行前缀匹配的速度。
    • 使用source_gen共享部分(如built_valuejson_serializable)的项目可以得到优化
  • 库循环处理优化 (v2.10.3):
    • 优化了分析驱动程序处理库循环的逻辑,显著加快了大型代码库的构建速度

所以一直到 2.13 版本,这些修改带来的提升,主要涉及:

  • 不需要序列化的资源图克隆

    • watchserve模式下,每次构建之间需要重置资源图(Asset Graph),老版本需要将图序列化到磁盘再读回的“往返”操作

    • 而 2.13 在graph.dart中引入了copyForNextBuild方法,直接在内存中克隆节点树,在频繁保存代码触发的增量构建场景就很实用

      AssetGraphcopyForNextBuild(BuildPhasesbuildPhases){returnAssetGraph._with(nodes:_nodes.clone(),// 直接克隆内存中的节点,避免序列化// ... 复制其他元数据previousInBuildPhasesOptionsDigests:inBuildPhasesOptionsDigests,inBuildPhasesOptionsDigests:buildPhases.inBuildPhasesOptionsDigests,// ...);}
  • 复用语法错误计算结果

    • Resolver 在处理库时需要检查语法错误,通过复用 Analyzer 已有的解析结果,可以避免重复计算

      Future<List<AnalysisResultWithDiagnostics>>_syntacticErrorsFor(LibraryElementelement,)async{finalparsedLibrary=_driver.currentSession.getParsedLibraryByElement(element);if(parsedLibraryis!ParsedLibraryResult)returnconst[];finalrelevantResults=<AnalysisResultWithDiagnostics>[];for(finalunitinparsedLibrary.units){if(unit.diagnostics.any((error)=>error.diagnosticCode.type==DiagnosticType.SYNTACTIC_ERROR)){relevantResults.add(unit);}}returnrelevantResults;// 利用 Analyzer 会话中的缓存结果}
  • 复用 Trigger 配置摘要

    • Builder 可以配置triggers(触发器),只有满足特定条件(如存在特定注解或导入)时才运行,v2.13.0 开始缓存这些配置的摘要,避免重复解析

      /// 只有当配置发生变化时,摘要才会改变latefinalDigestdigest=md5.convert(utf8.encode(triggers.toString()));

triggers 不是 2.13 才有,只是到 2.13 它的普及度已经比较高了。

这里的 Trigger 可以展开聊聊,例如在之前的 Builder 里,通常会扫描项目中的所有.dart文件,而通过触发器,Builder 可以声明:“只有当文件包含特定的import语句或特定的annotation(注解)时,才运行”,也就是:

  • 不满足触发条件时build_runner只进行基础的字符串匹配(不进行完整的符号解析),这比完整的类型解析(Resolving)快得多
  • 满足触发条件时:才会启动 Builder 并进入后续的解析流程

例如你正在开发或适配一个my_generator包,并且提供了一个my_builder生成器,你需要修改build.yaml,在builders定义中开启run_only_if_triggered,并增加顶层的triggers配置块:

# 1. 在生成器定义中开启触发模式builders:my_builder:import:"package:my_generator/builder.dart"builder_factories:["myBuilderFactory"]build_extensions:{".dart":[".g.dart"]}auto_apply:dependents# 核心配置:声明Builder 仅在被触发时运行defaults:options:run_only_if_triggered:true# 2. 定义具体的触发规则(顶层配置)triggers:# 格式为 "包名:生成器名"my_generator:my_builder:# 触发条件1:文件导入了特定的库(只需写库路径,系统会自动加上 package: 前缀)-"import my_generator/annotations.dart"# 触发条件2:文件使用了特定的注解名称-"annotation MyCustomAnnotation"

目前支持以下两种触发器:

  • import触发器
    • "import 路径/文件名.dart",检查源代码的指令中是否包含该库的导入语句,当用户必须导入注解库才能使用功能时非常高效
  • annotation触发器
    • "annotation 注解类名",检查源代码的所有声明上是否附带了这个名称的注解,即使没有显式导入(例如通过 export 间接导入)注解,只要检测到匹配的名称就可以触发

例如built_value就做了这个类适配,built_value只有在检测到import 'package:built_value/built_value.dart'@SerializersFor注解时才会运行 ,对于不相关的源文件,build_runner日志会显示为 “not triggered” 或 “skipped” :

所以,总结下来,到 2.13 版本,主要是做了以下三点来实现跳跃式的性能提升:

  • 内存化操作:资产图不再进行繁重的磁盘 I/O 序列化

  • 计算复用:解析器(Resolver)学会了复用分析结果,不再重复计算语法错误

  • 流程精简:通过 AOT 编译和更智能的触发器摘要管理,减少了 Builder 的预热和无效启动

所以,build_runner已经今非昔比,性能有了很大的提升,不过也不是说你完全什么都不做就可以体验,比如:

  • 第三方包需要在其build.yaml中将run_only_if_triggered设置为true,第三方包需要定义具体的triggers,系统可以根据触发器快速跳过不相关的源文件,从而不进入耗时的解析阶段
  • 开启 AOT 后,第三方 Builder 的代码中不能包含dart:mirrors,如果 Builder 使用了反射,它就不能进行 AOT 编译
  • analyzer依赖要升级到最新版本,2.13.0 通过BuildResolver深度复用了 Analyzer 的语法错误计算结果

那么,你觉得build_runner对你有用吗

链接

https://github.com/dart-lang/build/blob/master/build_runner/CHANGELOG.md

http://www.jsqmd.com/news/531475/

相关文章:

  • V4L2采集链路解析:从摄像头到用户态图像
  • [a股]一些很像的巧合 箱体
  • java毕业设计基于Spring Boot的阳光蛋糕店管理系统
  • Ubuntu下ESP-IDF环境搭建:巧用Gitee镜像与脚本,告别GitHub龟速下载
  • Dify混合检索优化落地手册(生产级SLA保障版):召回率、延迟、稳定性三重压测实录
  • 南北阁Nanbeige 4.1-3B助力研究:MATLAB数据分析与模型仿真结合
  • 5大场景掌握猫抓:网页资源捕获与媒体解析全方案
  • SDMatte高效抠图手册:复杂背景人像外物分离、发丝级保留实操步骤
  • OpenPDF中文PDF生成避坑指南:从字体加载到系统兼容性
  • EcomGPT-中英文-7B电商模型与Mathtype公式编辑器的联动:生成含数学公式的商品技术文档
  • 从自动驾驶到推荐系统:聊聊Pareto最优在AI产品中的那些“隐形”应用
  • 2026年横评后发现!全网顶尖的一键生成论文工具——千笔·降AIGC助手
  • 嵌入式启动进阶:除了FIT uImage,你的RK3399开发板还能怎么玩?对比传统uImage与FIT的实战选择
  • 在CentOS 7上用Docker Compose一键部署SeaTable私有云表格(保姆级避坑指南)
  • 滑铁卢大学发现的AI绘画加速密码:让重磅模型也能秒出图
  • AudioLDM-S与GitHub Actions的CI/CD集成实践
  • 丹青识画企业应用:为电子相册/版画定制自动生成雅致配文
  • 终极Windows与Office激活解决方案:KMS_VL_ALL_AIO完全指南
  • 系统优化的隐形陷阱与解决方案:Win11Debloat全方位调校指南
  • 突破动作捕捉技术壁垒:DiffSynth Studio实现视频到3D骨架的革新方案
  • Git版本控制实战:通义千问1.5-1.8B模型解读复杂操作与解决合并冲突
  • Ostrakon-VL-8B与嵌入式系统:基于STM32的智能餐盘原型开发
  • 别再为spaCy中文模型下载发愁了!手把手教你离线安装zh_core_web_sm(附GitHub下载链接)
  • 从数据到地图:手把手教你用QGIS可视化GEE导出的MCD64A1火点CSV,做出专业级分析图
  • LangGraph实战:用MemorySaver+ChatGPT API快速搭建一个能记住上下文的天气查询机器人
  • 叠加态程序员:同时被10家公司雇佣的黑暗操作
  • 深度解析ChatDev 2.0:构建下一代AI驱动的自动化开发流程与智能协作工具
  • 100%采样率引发的全线熔断:Spring Boot 链路追踪的性能绞杀与物理级调优
  • CF3.0单机版下载安装及人机8v8挑战模式完整使用教程
  • 深度解析|安科士ANBR-1414TZ光模块,工业级长距通信的性能密码