利用Claude AI重构Emacs插件:Rust重写实现千倍性能提升

本文详细记录了作者使用Claude AI将缓慢的Emacs Obsidian插件核心功能用Rust重写的全过程,从问题诊断到具体实现,展现了AI辅助编程如何高效解决实际问题,并大幅提升执行性能。

利用Claude构建个人软件

背景

上个月,我使用Claude将(部分)Emacs软件包移植到Rust,将执行时间缩短了1000倍或更多(在一个具体案例中:从90秒减少到约15毫秒)。

这是我经常做的一种“yak-shave”(为完成A必须先做B,为做B又必须先做C……的无尽循环),既出于专业需要,也为了完善我的个人计算环境。然而这一次,在我的监督下,Claude能够几乎完全执行整个项目,而我几乎不需要编写任何代码,与手动完成相比大大加快了项目进度。

开始这个项目时,我仍然预计Claude只能提供很小的帮助——大概相当于“一个更好的文档搜索引擎和更好的Stack Overflow”。尽管我在Anthropic工作,也不时阅读Simon Willison的文章,但我的期望显然已经大大过时了!

这次经历改变了我对LLM在软件工程和我自己工作中作用的一些看法。这些想法仍在展开,但本文试图捕捉我的经验,并在我思考如何更新我的行为、信念和期望时大声思考。

问题

在过去一年左右的时间里,我成为了Obsidian.md的重度用户,取代了Emacs org-mode文件和Workflowy的组合。我喜欢Obsidian的很多地方,但本文相关的关键细节是:(a)它直接将数据存储在本地磁盘的普通Markdown文件中,以及(b)有一个不错的Emacs模式与之交互。即使在笔记本电脑上,我仍然主要使用该应用程序,但能够进入Emacs进行复杂的文本操作,或者在编码时在编辑器中保留TODO列表,对我来说非常宝贵。

然而,随着我的知识库增长,我遇到了一个问题:obsidian.el变得慢得无法使用。打开任何笔记都会让Emacs挂起,起初是几秒钟,最终是一分钟或更长时间。问题原来是obsidian-update函数:obsidian.el定期重新遍历整个知识库,扫描每个笔记的标签和元数据,以更新其内部清单,而这个扫描(完全用elisp编写)最终变得非常慢。

我的计划

我是一个合格的elisp程序员,但没有性能分析或优化经验,而且GitHub问题中已经包含了一些其他人的尝试。我决定采取不同的方法。

我计划用Rust编写一个简短的程序来扫描和清点我的知识库,并将最小的元数据输出为JSON,并修改obsidian.el以使用该输出。我对Rust更熟悉,并且知道它通常性能很好,并且有出色的性能分析工具。

这样的解决方案可能很难包含在上游中,但我相当乐观地认为,这对于我自己的使用来说是一个相对小规模的项目,并且确信我可以通过这种方式解决自己的问题。

使用Claude

一时兴起,我决定看看Claude能为我解决多少这个问题。我并不指望它能有多好,但我还是决定试一试——也许Claude会给我惊喜。我决定从一个相对定义明确、目标清晰的请求开始,但要求它一次性完成大部分项目:

1
2
[上传文件: obsidian.el]
obsidian-update函数慢得令人痛苦。我想通过将相关逻辑移植到Rust程序来加速它,该程序将以JSON输出相关细节。你能编写这样一个Rust程序的第一个版本吗?它应该将知识库目录作为命令行参数,并且可以假设所有其他值都设置为默认值。

简而言之:这直接成功了。这是初始版本。

在单个提示中,没有思维链或其他显式推理,Claude阅读了大约1000行Emacs Lisp代码,识别了参与obsidian-update的约200行代码,识别了该代码与文件其余部分之间的数据边界,为该数据设计了JSON格式,然后将相关逻辑移植到大约150行Rust代码中。

生成的代码在第一次尝试时就编译并运行了,Claude甚至输出了一个包含所需依赖项的Cargo.toml文件。

我更进一步——它也能编写Emacs端的代码吗?我决定大胆一点,要求它使用Emacs advice来修补obsidian.el,目的是生成一些我可以包含在我的配置中而无需分叉或修补上游项目的东西。这个提示:

1
2
3
[上传文件: obsidian.el]
[上传文件: obsidian-scan/src/main.rs]
现在,请编写一些elisp代码来修补obsidian.el以使用obsidian-scan。不要编辑原始文件,请使用elisp的“advice”功能,这样我就可以在加载obsidian.el后在你的配置中包含你的代码片段。

这,也几乎直接成功了。它有一个(我发现的)bug:Emacs端在标签前加了一个#,但Rust已经包含了一个,导致标签像##writing。我让Claude修复它:

1
obsidian--tags-list最终得到带有双##的条目,例如##writing。你认为我们应该修复Rust代码还是elisp?选择一个并更新它。

Claude选择了elisp,并用简单的错误修复重写了文件。这是该项目所有剩余交互的特征——我会要求改进或修复,Claude会执行,最多只有一些小问题。

我又以这种方式来回了几次。值得注意的是,我要求Claude输出(文件 -> 标签)映射,并将其用于Emacs中的obsidian-tag-find。我现在已将Rust程序和elisp补丁都添加到了我的个人系统配置中,到目前为止我非常满意!

整个项目花了大约一个下午的时间。

反思

Claude在代码方面比我意识到的要好得多

我提出最初的提示时,大多没抱太大期望。但我听说Claude 3.5 Sonnet在编写代码方面有很大改进,而且总的来说,即使我预期会失败,我也会尝试定期给LLM一个机会,因为这是我发现真正了解其能力的唯一方法。

首先,我对最初的改写印象深刻:我以为Claude在给出清晰规格的情况下编写约100行代码会没问题,但从一千行elisp代码中提取隐含的规格,并根据该规格执行,这让我感到惊讶。我对迭代系统的过程进一步感到印象深刻;Claude在从上下文窗口阅读适量的代码,并以相当复杂的方式更新它们或对它们做出反应方面,继续表现得非常有效。我尝试的几乎所有事情都“直接成功了”。

我非常清楚,从理智上讲,这些模型正在以指数级的速度改进,6或12个月前形成的印象几乎肯定是严重过时的。但这种抽象的理解不足以形成对新现状的准确印象!我知道的唯一方法就是继续使用模型并与它们互动(并阅读其他这样做的人的报告)。这个事实正是为什么尽管预期失败,我还是尝试了这个提示,也是为什么我写下这个反思。

你“应该”对此印象深刻吗?

仅仅在写这篇文章的过程中,我就对自己对Claude的反应和想法感到着迷。

如上所述,我最初的反应是“惊讶和印象深刻”,Claude超出了预期。原始的obsidian.el大约有10,000个令牌长;直到2023年,Anthropic才发布了第一个可以处理这么多文本的公共模型。而对于我的任务,Claude不仅需要提取输入的单个事实或某些概括,还需要准确理解和解释文件中分散的许多小细节。直到最近,模型还做不到这一点!而且它是在一个单一的提示中完成的,没有任何思维链或显式推理!所以,是的,我印象深刻。

与此同时,即使在我起草这篇文章时,我也注意到自己头脑中出现了一种熟悉的“移动球门柱”现象!我发现自己的心态从惊讶和惊叹转变为一种几乎是厌倦的淡化,这至少出于两个角度:

  1. 好吧,但我不应该预见到这一点吗?我看到许多工程师对Claude 3.5 Sonnet的编码能力感到惊叹。我亲眼看到它解决了Anthropic的面试问题;这个练习涉及更多代码,但我也看到它阅读并回答关于更大代码段的问题。我很惊讶,但这更多是我的期望有问题,而不是模型本身那么令人印象深刻!
  2. 这个任务真的那么令人印象深刻吗?这个项目“只”涉及大约1000行代码;我参与的许多项目涉及数十万甚至数百万行代码!将elisp重写为Rust可能听起来令人印象深刻,但它也“只是”一个翻译问题:从根本上说是相当明确的,而语言模型通常在“看起来像”翻译的任务上非常强大。

我认为这两种观点——惊叹,以及淡化——在不同的方式上都是有效的!所有这些事情都是真的:

  • LLM在编码方面比一年前好得多。
  • 而且,更重要的是,它们目前的能力在2-3年前听起来绝对是像不可能的科幻小说。
  • 但同样,当前的模型“只是同一趋势的延续”;它们令人印象深刻,但与ML内部人士和领导者一年前预测的我们将会达到的水平相比,也没有那么不同。
  • 此外,当前的模型虽然令人印象深刻,但仍然远远不及专家级的人类表现。它们在正确的环境中是有用的,但(还!)不能“做我的工作”。

我认为,这基本上就是目前ML和LLM的本质。性能的指数级改进速度意味着我们永远处于惊叹和科幻般的体验与“哦,这只是一样的,有什么大不了的?”之间的中间状态。

我初步感到兴奋

我觉得这个项目让我对使用Claude/其他LLM辅助构建软件感到一种个人的兴奋和热情,这是我以前从未亲身感受过的。

具体来说,我感受到了编写软件来“挠自己的痒处”——解决我遇到的问题,构建我想要的工具——的快乐和兴奋,这是我多年来基本没有感受到的。在我年轻的时候,尤其是在本科期间,我周围都是我或我的朋友或像我们这样的人为了解决问题并给自己新的能力而编写的小软件碎片。我们感受到了巨大的力量、乐观和可能性,并真正相信我们可以塑造自己的计算环境,构建自己的工具,并打造我们想要的体验。这种精神存在于各种规模上,既描述了几位朋友使用的小脚本,也描述了当时许多最大、最成功的开源项目。

从那以后的几年里,世界变了,我也变了。我们的数据越来越孤立在各种由垄断企业拥有和运营的孤立云服务中,这些企业从漠不关心到恶意不等。一切都变得更加复杂,隐藏在层层抽象、复杂性和难以理解的OAuth背后。我也一样,我的职业生涯都在阅读关于在那些机构工作的文章,并学会了反射性地编写所有软件,就好像它将被大规模部署并存在多年一样。我甚至享受那种工作模式,以及建立健壮基础和寻找坚实基础的挑战,但再加上每次我必须弄清楚如何验证Google API或迁移到新版本的Javascript框架时感到的彻底绝望,这意味着我基本上已经停止为我自己和我的朋友构建小型软件了。

我现在感觉,一段时间以来第一次,那种旧的兴奋感在闪烁!Claude在掌握深奥的认证系统,或者了解React的当前版本,或者其他任何事情上,都远比我强。感觉就像我们生活在一个世界里——现在,或者至少很快——准确描述我希望存在的工具或脚本就足够了,Claude可以完成繁重的工作。如果时间流逝,API被弃用,我们需要迁移到某个框架的v27,Claude可能也能完成这项工作。见鬼,我们可能生活在一个每几年你就扔掉所有代码,然后在Claude的新会话中重新开始的世界里,使用相同的问题陈述但当前文档的副本,从头开始工作。我感到一丝希望和兴奋:我能重新学习构建小型软件,供我自己或我的社区使用吗?在那里Claude帮助处理了过去二十年来悄悄进入软件工程的无数麻烦,同时也让我停留在高水平的设计和概念层面,防止我的强迫性完美主义陷入细节?

我是在与工具作斗争,而不是与之合作

Claude.app / claude.ai感觉并不是为这个问题设计的。我花了很多时间从用户界面复制粘贴到磁盘文件中,并将错误或其他输出复制粘贴回Claude。

我发现很难弄清楚如何为我的对话维护适当的上下文。我最终得到了三个文件——原始的obsidian.el、Rust移植的main.rs和elisp适配器代码obsidian-scan.el。我不清楚我是否应该只使用一个长对话,或者尝试将它们全部拉入一个Project并定期创建新对话(何时?)。在单个长对话中工作很简单,但随着上下文窗口变长,速度会变慢,而且我还发现Claude最终会因为它在历史记录中看到的所有不同版本的文件而感到更加困惑。我尝试使用“添加到项目”按钮将Claude创建的工件复制回项目,但遇到了两个主要挑战:

  1. 由于我使用Claude快速迭代生成的工件,项目中的版本最终会过时。
  2. Claude不让我重命名/重新标题这些工件,所以我最终得到一个名为“Obsidian.el Performance Enhancement with Rust Scanner”的文件,这使得在提问或要求更改特定文件时很难引用它们。

这是一个目前有很多积极发展的领域;使用适当的MCP服务器或许多新的以AI为先的IDE之一可能会使这变得更简单。

然而,总的来说,这次经历强化了我的信念,即围绕LLM的工具和界面设计远远落后于实际能力,这是一个积极实验和发展的领域,除了任何未来的模型改进之外。

在定义的接口之间工作

在使用Claude时,我发现自己本能地选择将问题分解为具有相对定义明确且可测试接口的问题。例如,我不会要求它在一个查询中对Rust和elisp代码进行更改,而是要求将功能添加到Rust端,然后通过检查输出JSON来进行抽查,然后再要求相应的elisp更改。

我不确定这在多大程度上是必要的,但我认为我这样做既是为了Claude,也是为了我自己;基于接口边界和可测试性分解组件有助于我感觉理解并可以验证正在发生的事情,而且我知道如果事情变得混乱,如何干预或调试。这有点像我可能与一个更初级的工程师一起工作,至少部分是这样——自己做更多的系统设计或分解,然后委托定义明确的组件。

这肯定也有助于正确性和测试。我并不关心Claude的Rust代码与原始elisp的匹配程度,因为在我注意到错误之前我实际上并不在乎;但是“给定这个文件系统树,你应该找到这些笔记,包含这些标签和其他元数据”是一个非常容易测试的问题,如果我更关心,或者我发现了错误,我可以通过将它们编码到测试用例中并要求Claude优化代码来解决,而不是自己深入研究内部细节。

我预计——至少在不久的将来——这仍将是一个强大的模式:人类定义的接口边界或系统分解,以及Claude“在行间”工作。大概,随着时间的推移,Claude将能够几乎自主地处理越来越大的子系统,我们也将获得信心并建立技术来处理那些规格更模糊的子系统。

代码比以往任何时候都更便宜

一段时间以来,我越来越持有这样的观点:代码行本身相对便宜;软件开发中的宝贵资源更像是代码的理解,以及它做什么和为什么,以及知道编写什么代码的知识。

LLM似乎可能在短期内大大增加这种动态。你现在可以以仅仅几美分的价格生成数千行代码;但没有人会理解它们,而且LLM目前在进行调试、重构、设计和维护这些代码行方面,比生成它们更差。因此,代码比以往任何时候都更便宜,但我怀疑,至少在目前,洞察力和良好的架构设计以及理解将变得比以往任何时候都更有价值。

我怀疑这种变化也会影响哪些架构模式将变得有意义。也许编写易于删除的代码将比以往任何时候都更重要,这样你就可以扔掉整个模块,并要求Claude N+1或GPT M+1重新开始。也许构建“多语言”系统(包含多种不同编程语言)的门槛将大大降低,如果你能够收获不同语言生态系统或性能特征的好处,并且不必太担心雇佣熟悉四种深奥语言的工程师。这将很有趣。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计