
构建我们 Python 代码库的依赖关系图
引言
秉承高频交易的传统,Hudson River Trading (HRT) 快速发展。与工程中的任何指标一样,速度也有其弊端。在过去的五年里,由于注重“足够好”而非“完美”的工程文化,鼓励团队之间代码共享的协作工作环境,以及加速增长期,HRT 以研究为导向的 Python 代码库在规模和互联性方面呈指数级增长。随着我们的 Python 代码库增长到数百万行,导入时间增加了十倍,代码更改测试成本更高,lint 时间远远超出了实用范围——我们正在经历代码“缠结”的影响。
缠结
代码“缠结”是 HRT 员工借鉴 Dropbox 关于其 Python 代码库的出版物中对同一问题的描述而来的一个概念。当代码的依赖关系图有许多重叠的循环,并且代码库中不相关的部分通过间接和不直观的导入路径耦合在一起时,我们称之为“缠结”。缠结在任何大型代码库中(包括其他语言)都可能是一个问题!
根据我们的经验,缠结会影响运行时导入和静态分析(例如 mypy)的性能,并导致紧密的耦合,从而降低可靠性。在这些问题中,我们的用户认为运行时导入开销是最大的问题,因为它会减慢开发迭代循环并浪费数据中心的 CPU 时间。这可能对 HRT 来说比大多数其他 Python 公司更成问题,因为短寿命的 Python 进程在我们计算工作负载中占了相当大的比例。
缠结的负面影响会迅速增加——几个错误的导入就可能突然将数百个模块耦合在一起。导入开销的影响因缠结而放大,因为导入循环中的任何模块最终都会传递性地导入该循环中的所有模块(及其依赖项)。
虽然有些导入速度很快,但在许多情况下会产生显著的开销。开销悄悄进入的一种常见方式是通过文件系统访问——例如,现在已弃用的 pkg_resources 模块会爬取文件系统以查找资源。当在我们的网络文件系统上操作时,此过程变得特别有问题。另一个计算开销来源是加载庞大、单一的 C 扩展,例如 pandas 和 NumPy 等软件包,甚至专有扩展。此外,我们的一些纯 Python 模块会产生一系列耗时的静态初始化步骤,例如检测环境特性或处理类或回调的动态注册。
单独来看,这些都引入了可管理的导入工作负载;然而,在我们代码库最缠结的部分,复合效应可能导致大多数程序的导入时间超过 30 秒。这种开销减慢了开发迭代循环,并浪费了我们分布式计算环境中的 CPU 时间。
依赖管理
总的来说,我们解决缠结问题的方法是建立并维护一个分层架构,其中较低层的模块不从较高层的模块导入。建立适当的分层有助于调用者只导入他们需要的东西。
理想情况下,我们的依赖关系图应该类似于有向无环图,其中模块按其分配的层进行拓扑排序。然而,在实践中,只要循环相对较小并包含在一个(子)包中,一些循环是可以接受的。
过渡到更好的依赖管理范例需要识别当前缠结的原因,重构代码库以重新组织依赖关系,并实施依赖关系验证以避免未来的回归。所有这些工作都必须在不暂停代码库开发的情况下完成!
缠结工具:理解缠结
一旦我们了解到缠结是我们许多开发人员体验问题的根源,我们便着手构建一个分析代码库依赖关系图的工具包——Tangle Tools。Tangle Tools 分析 Python 源代码以生成整个代码库的依赖关系图(节点对应模块,边对应导入)。然后,我们的用户可以利用命令行和浏览器界面来发现、导航和重构依赖关系。
典型的解缠结工作流包括:
-
查找不需要的传递依赖
-
从源头追踪到不需要的依赖的导入路径
-
计算源和依赖之间的流量网络
-
识别哪些边在移除后会减少流量
-
重构导入以断开源和依赖的连接
-
利用代码转换自动化常见的重构(例如将符号移动到新模块并更新现有引用)
-
实施依赖验证以避免回归
-
用户编写依赖规则来约束其模块的依赖关系
-
这些规则在我们的持续集成管道中进行检查
所有这些如果不是广泛使用开源库,都是不可能实现的!我们使用 Python 内置的 ast 库来解析我们的 Python 源代码以获取导入。这项解析工作通过 stdlib 强大的 concurrent.futures 模块并行化,使我们能够快速处理数千个模块。在底层,我们使用 networkx 的有向图数据结构和丰富的图算法库——我们发现流算法特别有用。最后,我们使用 libcst 库执行自动化源代码重构,编写为具体语法树的转换。
结论
通过开发这些依赖管理和重构工作流,我们在解缠结方面取得了显著进展。以前,查找导入依赖关系是一个缓慢的手动过程,重构依赖关系则是一场打地鼠游戏。现在,我们可以导航完整的依赖关系图,并找到高效的重构方法,解决缠结的根本原因。
认识作者
George Farcasiu - 核心开发人员
- George Farcasiu 参与了 HRT Python 生态系统中的一系列项目,担任 Python 静态分析和依赖管理工具的创建者、分布式计算框架和环境的贡献者以及构建/测试/持续集成开发工具的维护者。
Noah Kim - 核心开发人员
- Noah Kim 主要关注 HRT 对 CPython 解释器的使用。他还是 Tangle Tools 的现任维护者,该工具是改善公司 Python 包生态系统更广泛努力的一部分。
Jacob Brugh - 核心开发人员
- Jacob Brugh 最近的关注领域包括改进 HRT 用于我们 C++ 交易库的 Python 绑定代码的性能,以及开发内部工具,使我们能够大规模执行高效的静态分析。
Jiahao Li - 核心开发人员
- Jiahao Li 参与了涉及分布式计算集群和构建/测试平台的各种项目。最近的项目包括彻底改革 HRT 的测试环境,以提供依赖跟踪和更智能的测试选择。