返回

文章详情

让 ast.walk 快速 220 倍

Hacker News2026年6月16日 16:25

在我们的 AI reflex 应用构建器中,我们生成大量的 Python 代码。有时,这些代码生成以相当简单的方式失败;例如,关键字参数后面跟位置参数、在异步生成器中返回带值的结果、使用我们框架之前版本的过时语法等。运行 reflex compile 最终会发现所有这些错误,但它一次只发现一个问题。这意味着如果 AI 出现了多个错误,我们将大幅增加延迟,而这些错误本可以相对简单地修复。因此,我们决定使用 linter 是修复这一问题的最佳方法。由于我们需要添加特定于 reflex 的规则,我们无法使用现有的 linter,只能构建自己的。我们最初的 linter 看起来很简单:不幸的是,这种方法很快遇到了性能问题,因为我们正在处理大量生成的代码。值得注意的是,上述代码中最慢的部分不是 isinstance 检查,而是 ast.walk。当然,有许多优化方法并没有使 ast.walk 更快,我们首先实现了那些“低悬果实”改动。但是,我们很快意识到,如果不挑战使 walk 本身更快,就很难让 linter 显著提速。然而,遍历一个抽象树(在本代码中,仅仅是一个普通树)并不一定要慢。在我的设备上,遍历 difflib 模块花费了大约 2 毫秒,处理了大约 7000 个节点。单独来看,这并不算糟糕,但快速累积起来就会变得很慢。粗略计算每个节点大约需要 285 纳秒,大约一千个 CPU 循环 - 远远超过了如此简单的遍历所需的时间。那么,ast.walk 究竟在做什么呢?这里首先要注意的是使用了 yield 。生成器和 yield 语法是 Python 的一个强大特性,但它们带来了巨大的代价:在我们正在消耗完整列表的热路径中,反复暂停和恢复执行循环。当然,这样可以节省内存,但这并不是我们此刻的问题,尤其是在该列表会被清理的情况下。如果我们简单地存储一个列表,并不断向其添加元素,我们可以将这一点最小化:但在运行之后,我意识到这只带来了大约 5% 的提升。远低于我的预期,让我不禁想:iter_child_nodes 在做什么?啊,又是一个生成器。如果将其内联,我们应该能看到一些不错的性能提升:我们确实得到了我们想要的提升,大约 25%。这让人不得不问:剩余的 75% 到哪里去了?在这个话题上,iter_fields 是什么呢?你好 Python?又来了,另一个生成器。我们还正在 yield 一个元组,而我们最终没有用到,因为我们只关心值,而不是名称。getattr(node, field, None) 应该更快,因为当委托给 CPython 时,异常处理可以更快。让我们将这两者结合起来:这使得我们得到了大约 50% 的累积提升。这消除了我们从 ast 模块调用的最后一个函数,所以这里只有我们值得指责。快了两倍并不错,但以 2 倍速度遍历被称为冲刺吗?我觉得不是。让我们进一步推动这一点。我们可以在同一个调用中读取 _fields 并检查子类化。在 ._fields 中存在的其他对象不能出现在格式良好的 ast 树中,因此我们应该是安全的。尽管如此,这仅增加了另一个增量提升,也许是累积 55%。此时,我已经达到了 Python 能做的极限。将其改为迭代而非递归几乎没有效果。但 Python 还有一个最后的技巧:绑定。它允许我们以本机机器代码(或编译到本机机器代码的形式)编写这个逻辑。虽然我本可以在这里使用 C,但我选择使用 rust,仅仅是因为我习惯于此。让我们做一个简单的逐字翻译:我在使用 cast_unchecked,因为如果我使用普通的类型转换,PyO3 将进行繁重的类型检查。有时这种类型检查是有用的,但在这里并没有。除此之外,上面的代码并没有太大的不同,getattr_opt 是 getattr(..., ..., None)。这带来了大约 78% 的累积改进。太好了!这也让我们可以做一些更有趣的事情。看看,因为我们正在进行大量的 getattr,它编译为读取字典,我们可以简单地遍历字典本身。在 Python 中,这个字典称为 __dict__。我们可以简单地在内存偏移中读取它:我们还可以改善我们的子类检查。只有 132 个类是 ast.AST 的子类,所以我们可以存储所有这些类的内存地址在集合中,然后简单地检查成员资格(我起初拿 fastset 这里,但它为小而稠密的整数构建,对于 64 位指针值会出现恐慌,所以就是普通的哈希集)。然后我们将两者结合起来:这使得我们达到了约 93%。总的来说,这是约快了 14 倍。我们剩下的唯一 CPython 调用是在 BorrowedDictIter 内部,它调用 PyDict_Next。这个函数不是特别慢,但做了很多检查和引用计数,而我们的 Rust 思维无法理解。那么让我们继续...

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡