我拥有47年的编程经验,而且对新鲜事物依然充满热情,所以很自然地,我对GenAI(基因人工智能)在未来软件领域将扮演的角色(如果有的话)很感兴趣。但我的兴趣还不足以让我真正去学习必要的技能并亲自尝试。总有一天,总有一天。不过这并不重要;另外两个人未经我允许就把Claude应用到了我目前的代码测试平台Quamina上。以下是第一个故事。即使会有人因此生气,我还是要分享出来。
为什么要分享?
因为我们这个行业关于这个话题的争论既荒谬又有害。Gas Town和Moltbook阵营与“人工智能就是个混蛋”阵营之间似乎不可能进行任何有意义的对话。所以,我今天不参与讨论。这纯粹是轶事:当 Rob 将 Claude 应用于 Quamina 时发生了什么。我将避免修辞(语言学意义上的修辞,即旨在说服的语言),尤其避免论战(旨在攻击的语言)。我保证不久后会得出结论,只是今天不行。
事情是这样的……
我认识一个叫罗伯·塞耶的人,我们认识很多年了,甚至在 IETF 的工作场合有过一两次碰面。之前我们从未合作过写代码。从一月中旬开始,他源源不断地提交 PR ,其中大部分我最终都接受并合并了。
最终结果是,Quamina 在几个旨在衡量典型任务的基准测试中速度大约提高了一倍。
技术细节
Quamina 的具体功能和特性详见README文件。为了便于讨论,我们暂且忽略其他细节,只关注它最重要的两个 API: AddPattern()用于向实例添加模式(字面值或正则表达式), MatchesForEvent用于处理 JSON 数据块并返回匹配到的模式。它的速度非常快,而且添加的模式数量与匹配速度之间的相关性并不高。
Quamina 基于有限自动机(确定性和非确定性),本技术细节部分的其余部分将使用 NFA 和 DFA 术语,对此我深表歉意。
对于这种既非 I/O 密集型也非 UI 密集型的代码,性能的关键在于选择合适的算法。一旦算法选对了,剩下的就主要是内存管理了。显然,在 Quamina 中, AddPattern调用需要分配内存来保存有限自动机。但我希望MatchesForEvent调用不需要。
Go 语言内置的数据结构只有“map”(即哈希表)和“slice”(即可追加数组)。(对于从 Java 转过来的用户来说,Java 提供了几十种列表和哈希表,这起初可能会让他们感到震惊,但大多数 Go 爱好者最终都会认同 Go 的理念,认为 Java 是错误的。)在真正优化良好的代码中,你希望看到所有时间都花在你自己编写的逻辑上,或者花在向切片追加数据和更新 map 上。
在优化程度较低的代码中,性能分析器会显示你在运行时例程(名称包含“malloc”)和垃圾回收器上花费了大量时间。现在,map 和 slice 都会根据需要自动增长,这很好,但当你试图最小化内存分配时就另当别论了。原来 slice 是有容量限制的,只要你追加的内容数量小于容量限制,就不会分配内存,这很好。因此,所有优化过 Go 代码的人都会用到以下两个标准技巧:
-
创建新切片时,请确保其容量足以容纳所有要添加的内容。是的,这可能比较困难,因为你很可能要用它来存储大小不可预测的输入数据,因此……
-
创建新切片后,将其保留,每次输入记录后清除它,其容量自然会增长,直到足够大以容纳所有其他记录,然后您就再也不用分配空间了。
那些公关稿
背景:Quamina 配备了我认为相当不错的单元测试套件和多个基准测试。
我开始收到 Rob 的 PR,最初,它们几乎 100% 都沿着那两条常用的 map-slice 路径进行优化,而我之前并没有注意到这些优化机会。这些 PR 都很不错,注释详尽,代码合理,测试覆盖率也没有降低。在我要求查看基准测试结果以证明这些改进并非只是理论上的之后,他们开始提供基准测试结果。我发现了一些需要改进的地方,但我和 Rob 很快就解决了这些问题。
最终,我并不反对合并它们,但我确实很好奇它们是如何构建的。所以我问了。
工作流程
罗布一开始就告诉我,这些结果基本上都是克劳德生成的。我问他工作流程,他回答说:“我可能会说‘让我们在这个基准测试中,在主分支和这个分支上,分别对内存和 CPU 进行一些分析’。它会给出一些好的想法和一些不好的想法,然后我从中选择。”
另外:“可能有点反直觉的是,我可以很快地切换话题。比如,你留言,我直接告诉克劳德修改,因为你说得对。有时候我会手动编辑,但通常都能做到接近完美(他们称之为‘一次完成’)。我只要保持对话畅通,就能接着上次的话题继续。”
这是克劳德和罗布对话的片段。你可能需要放大查看。

并非千篇一律
然后我得到了一个惊喜,因为 Claude 和 Rob 发现了两个不在标准列表中的重大改进。首先:遍历一个 NFA 时,对于每个状态,都需要计算其“ε 闭包”,即通过 ε 转换可以传递到达的其他状态的集合。我已经建立了一个缓存,以便在计算 ε 闭包时将其保存下来。C&R 指出:“ε 闭包是自动机结构的属性,而不是输入数据的属性。一旦添加了模式并构建了 NFA,任何给定状态的 ε 闭包都是固定的,不会改变。” 因此,最好在构建 NFA 时就计算并保存它。
这比听起来还要好,因为(基于 Quamina 并发模型的合理性)我的闭包缓存是按线程的,而新的 epsilon 闭包是全局的,所有线程只存储一次。不错,也并非微不足道。
其次,在计算这些闭包时,必须对关键函数进行记忆化,以避免陷入 NFA 循环。我之前用集合(set)实现了这一点,在 Go 语言中,集合可以实现为map[whatever]bool &C 发现,如果给每个状态添加一个“闭包生成”整数字段,并维护一个全局闭包生成值,就可以避免使用集合,代价是每个状态需要一个整数。基准测试证明了这种方法的有效性。
在我撰写这篇文章时,又收到了一个标题令人振奋的 PR :“kaizen:匹配路径上的无分配”。
改善?
它的理念是通过逐步引入小的改进,最终实现显著的提升。我们尝试用这个术语来标记那些不改变语义,但能提升性能、可靠性或其他方面表现的 Quamina PR。
但是 GenAI 是坏的?!
没错,他们是这么说的。去再读读那篇卑鄙无耻的檄文吧。
不过,我暂时先不发表这个小案例的结论,因为还有另一个案例要发布。等我把这两个案例都发布之后,我才会开始讨论这种行为是否卑鄙。
原文: https://www.tbray.org/ongoing/When/202x/2026/02/06/Q-Plus-C-Ch1