CQ9节拍

构建Python代码库的依赖关系图
主题
发表

2023年6月5日

介绍

为了保持我们高频交易的根基,CQ9行动迅速. 与工程中的任何指标一样,速度也有其权衡. 在过去的五年里, CQ9在其面向研究的Python代码库的规模和互联性方面呈指数级增长, 由于一种争强好胜的工程文化的结合,这种文化通常看重“足够好”而不是“完美”,“我们的协作工作环境鼓励团队之间的代码共享, 以及一段加速增长的时期. 随着我们的Python代码库增长到数百万行, 进口时间增加了一个数量级, 代码更改的测试成本变得更高, 而lint时间的增加远远超出了有用的范围——我们正在经历代码“缠结”的影响.”

缠绕

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

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

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

虽然有些导入非常快,但在许多情况下会产生很大的开销. 开销增加的一种常见方式是通过文件系统访问——例如,现在已弃用的方法 pkg_resources 模块爬取文件系统以定位资源. 当在我们的网络文件系统上操作时,这个过程变得特别有问题. 计算开销的另一个来源是庞大的负载, 像pandas和numpy这样的单块C扩展,甚至是专有扩展. 另外, 我们的一些纯python模块会产生一系列昂贵的静态初始化步骤,如检测环境特征或处理类或回调的动态注册.

在隔离, each of these introduces manageable import overhead; however, 在我们代码库中最复杂的部分, 对大多数程序来说,复合效应可能会导致超过30秒的导入时间. 这种开销减慢了开发迭代循环, 并且在我们的分布式计算环境中浪费CPU时间.

依赖关系管理

在高水平上, 我们解开缠结的方法是建立和维护一个分层的体系结构,在这个体系结构中,低层的模块不会从高层的模块导入. 建立适当的分层可以帮助调用者只导入他们需要的内容.

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

向更好的依赖项管理范式的转换需要识别当前导致缠结的原因, 重构代码库以重构依赖关系, 并进行依赖验证以避免未来的回归. 所有这些工作都必须在不暂停代码库开发的情况下完成!

纠结工具:了解纠结

一旦我们理解了这种纠缠隐藏着许多开发者体验问题, 我们开始构建一个工具包,用于分析代码库的依赖关系图. Tangle Tools分析Python源代码以生成整个代码库的依赖关系图(节点对应模块,边对应导入). 然后,我们的用户可以利用命令行和浏览器界面来发现, 导航, 重构依赖关系.

一个典型的解缠工作流程包括:

  • 找到不需要的传递依赖项
  • 跟踪从源到不需要的依赖项的导入路径
    • 计算源和依赖项之间的流网络
    • 确定移除哪些边会减少流量
  • 重构导入以断开源与依赖项的连接
    • 利用代码转换来自动执行常见的重构(例如.g. 将符号移动到新模块并更新现有引用)
  • 将依赖项验证放在适当的位置以避免回归
    • 用户编写依赖规则来约束其模块的依赖关系 
    • 在我们的持续集成管道中检查这些规则

如果没有广泛使用开源库,这一切都不可能实现! 我们使用Python的内置ast库来解析Python源代码中的导入. 该解析工作通过标准库的强大功能并行化 并发.期货 模块,允许我们快速处理上千个模块. 在引擎盖下我们使用 networkx的有向图数据结构和广泛的图算法库-我们发现流算法特别有用. 最后,我们使用 libcst 库来执行自动的源代码重构, 作为具体语法树的转换编写.

结论

通过开发这些依赖关系管理和重构工作流,我们已经能够在解开缠结方面取得重大进展. 以前, 查找导入依赖关系是一个缓慢的手动过程, 重构依赖关系是一场打地鼠游戏. 现在我们可以浏览完整的依赖关系图,并找到解决缠结根源的有效重构.


1 http://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python
2 http://en.wikipedia.org/wiki/Coupling_(computer_programming)

认识作者

George Farcasiu——核心开发者

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

诺亚金-核心开发人员

Noah Kim主要关注CQ9对CPython解释器的使用. 他也是Tangle Tools的现任维护者, 这是改善公司Python包生态系统的更广泛努力的一部分.

雅各布布鲁-核心开发人员

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

李家豪-核心开发人员

李家豪从事各种项目,包括分布式计算集群和构建/测试平台. 最近的项目包括彻底检修CQ9的测试环境,以提供依赖跟踪和更智能的测试选择.

不要错过任何一个节拍

关注我们,了解CQ9在工程、数学和自动化方面的最新信息.