注意:虽然 JavaScript 对于本网站来说不是必需的,但您与内容的交互将受到限制。请打开 JavaScript 以获得完整体验。

构建 Python 代码库的依赖关系图

介绍

秉承我们在高频交易方面的根基,哈德逊河交易 (HRT) 发展迅速。与工程中的任何指标一样,速度也有其权衡。在过去的五年里,HRT 的研究型 Python 代码库的规模和互连性呈指数级增长,这是由于斗志旺盛的工程文化的结合,这种文化通常更看重“足够好”而不是“完美”,我们的协作工作环境鼓励团队之间的代码共享,以及一段加速增长的时期。随着我们的 Python 代码库增长到数百万行,导入时间增加了一个数量级,代码更改的测试成本变得更加昂贵,并且 lint 时间的增加远远超出了有用的范围——我们正在经历代码“混乱”的影响。

纠结

代码“缠结”是 HRTers 借鉴自Dropbox 出版物中关于他们自己的 Python 代码库的同一问题的描述的概念。当代码的依赖图有许多重叠的循环,并且代码库的不相关部分通过间接且不直观的导入路径耦合时,我们将代码称为“纠结”。在任何大型代码库(包括其他语言)中,缠结都可能是一个问题!

根据我们的经验,缠结会影响运行时导入和静态分析(例如 mypy)的性能,并导致紧密耦合,从而降低可靠性。在这些问题中,我们的用户发现运行时导入开销是最大的问题,因为它减慢了开发迭代循环并浪费了数据中心的 CPU 时间。对于 HRT 来说,这可能比大多数其他 Python 商店更成问题,因为短暂的 Python 进程占我们计算工作量的很大一部分。

缠结的负面影响可能会迅速增加——一些错误的导入以及突然数百个模块耦合在一起。导入开销的影响会因缠结而放大,因为导入循环中的任何模块最终会传递导入该循环中的所有模块(及其依赖项)。

虽然某些导入速度非常快,但在许多情况下会产生大量开销。一种常见的开销方式是通过文件系统访问——例如,现在已弃用的 pkg_resources 模块会爬行文件系统来定位资源。当通过我们的网络文件系统操作时,这个过程变得特别有问题。计算开销的另一个来源是通过 pandas 和 NumPy 等包加载庞大的整体 C 扩展,甚至是专有扩展。此外,我们的一些纯 Python 模块需要执行一系列成本高昂的静态初始化步骤,例如检测环境特征或处理类或回调的动态注册。

单独来看,这些都引入了可管理的导入工作负载;然而,在我们的代码库中最复杂的部分,对于大多数程序来说,复合效应可能会导致导入时间超过 30 秒。这种开销会减慢开发迭代循环,并浪费分布式计算环境中的 CPU 时间。

依赖管理

在较高的层面上,我们的理清方法是建立和维护分层架构,其中较低层的模块不会从较高层的模块导入。建立适当的分层可以帮助调用者仅导入他们需要的内容。

理想情况下,我们的依赖图应该类似于有向无环图,其中模块按其分配的层进行拓扑排序。然而,在实践中,某些周期是可以接受的,只要它们相对较小并且包含在一个(子)包中即可。

过渡到更好的依赖关系管理范式需要识别当前的混乱原因,重构代码库以重构依赖关系,并进行依赖关系验证以避免未来的回归。所有这些工作都必须在不暂停代码库开发的情况下完成!

缠结工具:了解缠结

一旦我们理解了缠结是我们许多开发人员遇到的问题的根源,我们就开始构建一个用于分析代码库依赖关系图的工具包——缠结工具。Tangle Tools 分析 Python 源代码以生成整个代码库的依赖关系图(节点对应于模块,边对应于导入)。然后,我们的用户可以利用命令行和浏览器界面来发现、导航和重构依赖项。

典型的理清工作流程包括:

  • 寻找不需要的传递依赖

  • 跟踪从源到不需要的依赖项的导入路径

  • 计算源和依赖项之间的流网络

  • 确定哪些边缘被移除后会减少流量

  • 重构导入以断开源与依赖关系

  • 利用代码转换来自动执行常见重构(例如,将符号移动到新模块并更新现有引用)

  • 进行依赖性验证以避免回归

  • 用户编写依赖规则来约束其模块的依赖关系 

  • 这些规则在我们的持续集成管道中进行检查

渲染摘要图

如果没有广泛使用开源库,这一切都是不可能的!我们使用 Python 的内置 ast 库来解析 Python 源代码以进行导入。这个解析工作是通过 stdlib 强大的并发.futures 模块并行化的,使我们能够快速处理数千个模块。在底层,我们使用networkx的有向图数据结构和广泛的图算法库——我们发现流算法特别有用。最后,我们使用 libcs​​t 库执行自动源代码重构,将其编写为对具体语法树的转换。

结论

通过开发这些依赖管理和重构工作流程,我们已经能够在理清问题上取得重大进展。以前,查找导入依赖项是一个缓慢的手动过程,而重构依赖项就像打地鼠游戏。现在我们可以浏览完整的依赖关系图并找到有效的重构来解决缠结的根本原因。

认识作者

George Farcasiu - Core Developer

  • George Farcasiu 作为 Python 静态分析和依赖管理工具的创建者、分布式计算框架和环境的贡献者以及构建/测试/持续集成开发工具的维护者,参与了 HRT Python 生态系统中的一系列项目。

Noah Kim - Core Developer

  • Noah Kim 主要关注 HRT 对 CPython 解释器的使用。他也是 Tangle Tools 的当前维护者,该工具是改善公司 Python 包生态系统的更广泛努力的一部分。

Jacob Brugh - Core Developer

  • Jacob Brugh 最近关注的领域包括提高 C++ 交易库的 HRT Python 绑定代码的性能,以及开发内部工具,使我们能够大规模执行高效的静态分析。

Jiahao Li - Core Developer

  • Jiahao Li 从事涉及分布式计算集群和构建/测试平台的各种项目。最近的项目包括彻底改造 HRT 的测试环境,以提供依赖性跟踪和更智能的测试选择。

本文最初出现在HRT Beat中。