gstack 性能审查专家(Performance Specialist)详解:并行代码审查中如何在落地前捕捉性能隐患
本篇指南围绕 gstack 仓库中 review/specialists/performance.md 展开,系统讲解 gstack /review 工作流里"性能审查专家"的激活条件、JSON 输出契约、七大性能问题类别,以及它在并行子代理审查(Review Army)中如何被调度、去重、评分并进入 Fix-First 修复流水线。读完后,你可以理解这条检查清单如何把 N+1 查询、缺失索引、算法复杂度、前端包体积、渲染性能、无分页和无界返回、以及异步上下文阻塞这七类问题转化为机器可解析的结构化发现(finding),并能结合仓库源码验证其真实的调用链与测试佐证。
1. 它在 gstack 审查体系中的位置:一个被条件触发的并行审查员
review/specialists/performance.md 是 gstack /review 技能"Review Army"(并行专家审查军团)中的专家检查清单之一。/review 的完整工作流定义在 review/SKILL.md,其中 Step 4.5(见 review/SKILL.md)负责检测项目技术栈与变更范围,然后决定派遣哪些专家子代理。性能专家与 testing、maintainability、security、data-migration、api-contract、design 并列,每个专家都是拥有全新上下文(fresh context)的独立子代理,互不干扰地读取 diff 并输出结构化发现。
性能专家的激活条件在原文档第一行就写明:
Scope: When SCOPE_BACKEND=true OR SCOPE_FRONTEND=true
这两个标志由 bin/gstack-diff-scope 脚本产生。从源码看(bin/gstack-diff-scope),该脚本收集三类文件路径的并集——已提交的 diff、工作区未提交改动、未跟踪的新文件(注释指出这是 #2299 修复,确保 /ship 在提交前做范围检测时能看到未提交工作),然后对每个文件做独立的类别匹配(#2299 之前是 first-match-wins,导致 Button.test.jsx 只触发 FRONTEND 不触发 TESTS,行为取决于文件顺序):
- FRONTEND 信号:
*.css/*.scss/*.less/*.sass/*.pcss、*.tsx/*.jsx/*.vue/*.svelte/*.astro、*.erb/*.haml/*.slim/*.hbs/*.ejs、*.html、tailwind.config.*、postcss.config.*,以及app/views/*、*/components/*、styles/*、css/*、app/assets/stylesheets/*等目录模式(bin/gstack-diff-scope); - BACKEND 信号:非前端组件/视图文件中的
*.rb/*.py/*.go/*.rs/*.java/*.php/*.ex/*.exs,以及*.ts/*.js/*.mjs/*.cjs/*.mts/*.cts(#1810 修复补全了 ESM/CJS 扩展名,否则纯 ESM 的 PR 会跳过整个后端审查)(bin/gstack-diff-scope)。
只要 diff 里出现前端或后端文件,性能专家就会被选中。这与检查清单本身的结构呼应:七个大类里,"Bundle Size Impact"和"Rendering Performance"明确标注为前端专属,其余类别则覆盖后端(数据库、异步上下文、分页)。相关行为在单元测试 test/diff-scope.test.ts 中被逐类验证,例如 styles.css + component.tsx 触发 SCOPE_FRONTEND=true,app.rb + service.py 触发 SCOPE_BACKEND=true。
除范围信号外,还有几层门控决定专家是否真正运行(见 review/SKILL.md 与生成该段落的模板解析器 scripts/resolvers/review-army.ts):
| 门控 | 规则 |
|---|---|
| 小 diff 跳过 | DIFF_LINES < 50(插入+删除行数)时跳过所有专家,输出 "Small diff (N lines) — specialists skipped." |
| 自适应门控 | gstack-specialist-stats 标记为 [GATE_CANDIDATE](10 次以上派遣且 0 发现)的专家会被自动跳过;性能专家不属于 [NEVER_GATE] 保险型专家(那是 security 和 data-migration 的待遇) |
| 强制标志 | 用户提示中包含 --performance 或 --all-specialists 时,无视门控强制派遣 |
因此,"性能专家什么时候跑"的完整答案不是简单的"diff 有前后端文件",而是:diff 行数 ≥ 50,且 SCOPE_BACKEND 或 SCOPE_FRONTEND 为 true,且未被自适应门控,或用户显式传了 --performance。
2. 输出契约:每行一个 JSON 发现,否则只说 NO FINDINGS
原文档对输出格式的规定非常严格,这也是 Review Army 能自动合并结果的前提:
Output: JSON objects, one finding per line. Schema:
{"severity":"CRITICAL|INFORMATIONAL","confidence":N,"path":"file","line":N,"category":"performance","summary":"...","fix":"...","fingerprint":"path:line:performance","specialist":"performance"}
Optional: line, fix, fingerprint, evidence, test_stub.
If no findings: output `NO FINDINGS` and nothing else.
逐字段解读:
- severity:
CRITICAL或INFORMATIONAL,两级严重度。CRITICAL 发现倾向于进入"询问用户"(ASK)流程,INFORMATIONAL 发现倾向于自动修复(AUTO-FIX)——这套 Fix-First 启发式定义在 review/checklist.md; - confidence:1-10 的置信度评分,决定发现最终是展示、带保留意见展示、进附录还是完全抑制(详见第 7 节);
- path / line:问题定位到文件与行号;
- category:固定为
performance,与指纹(fingerprint)一起参与去重; - summary / fix:一行问题描述 + 一行建议修复,符合 gstack 审查体系"one line problem, one line fix"的简洁原则;
- fingerprint:格式为
path:line:performance。Step 4.6 合并逻辑规定:若发现未带 fingerprint 字段,则用{path}:{line}:{category}(有 line 时)或{path}:{category}补算(scripts/resolvers/review-army.ts); - test_stub(可选):如果能写一个可捕获该问题的测试,就用项目检测到的测试框架(Jest/Vitest/RSpec/pytest/Go test,由 Step 4.5 的
TEST_FW检测逻辑确定,见 review/SKILL.md)写一个最小骨架放入此字段。任何带 test_stub 的发现都会被强制改判为 ASK,由用户决定是否创建该测试文件(review/SKILL.md); - NO FINDINGS:无发现时只能输出这四个字加换行,不允许任何前言、总结或评论——这保证了主代理可以按行解析、按 JSON 合法性过滤(非法行直接跳过)。
3. 类别一:N+1 查询(N+1 Queries)
原文档列出的四类 N+1 模式:
- 循环中遍历 ORM 关联而未预加载(ActiveRecord/ORM associations traversed in loops)——修复手段是各框架的 eager loading:Rails 的
.includes、SQLAlchemy 的joinedload、Prisma 的include; - 迭代块(each / map / forEach)内的数据库查询,可改为批量查询;
- 嵌套序列化器触发懒加载关联;
- GraphQL resolver 逐字段查询而非批处理(检查是否使用了 DataLoader)。
这类问题在 gstack 中不是孤立的:主检查清单 review/checklist.md 的 Pass 1(CRITICAL)"SQL & Data Safety"里同样列出 "N+1 queries: Missing eager loading",并且 Fix-First 启发式明确把 "N+1 queries (missing eager loading)" 归入 AUTO-FIX——即"机械、资深工程师不会讨论就动手修"的那一类,代理会直接补上 .includes 之类的预加载并输出 [AUTO-FIXED]。换句话说,性能专家报出的 N+1 发现大概率会被自动修复,而不是抛给用户选择。
仓库里还有一条端到端测试专门验证这条路径:test/skill-e2e-review-army.test.ts 的 "Review Army: N+1 Performance" 用例(#L116-L180)构造了一个 Ruby 仓库,写入故意的坏代码 fixture test/fixtures/review-army-n-plus-one.rb:
# N+1 query example — intentionally bad for testing
class PostsController
def index
@posts = Post.all
@posts.each do |post|
# N+1: queries Author table for every post
puts post.author.name
# N+1: queries Comments table for every post
puts post.comments.count
end
end
end
测试提示词要求代理"读 review-specialists/performance.md 并应用到 diff",然后断言输出中必须出现 N+1 相关关键词(n+1 / eager / includes / preload / query / loop)。这正好覆盖检查清单的第一、二类模式:each 循环内对 post.author 与 post.comments 的懒加载遍历。该 fixture 展示了性能专家期望识别的最小 N+1 样本:一条集合遍历 + 循环体内对每个元素发起关联查询。
4. 类别二:缺失数据库索引(Missing Database Indexes)
检查点面向"本次 diff 新引入的查询形状",而非全库索引审计:
- 新增的 WHERE 子句落在无索引列上(要求检查 migration 文件或 schema 确认列上是否有索引);
- 新增的 ORDER BY 作用于非索引列;
- 组合查询(WHERE a AND b)但缺少复合索引;
- 新增外键列未带索引。
这条类别隐含的工作方式是"读 diff 之外的代码":判断索引是否存在必须打开 migration/schema 文件,这与 review/SKILL.md 中对 Enum & Value Completeness 类别的要求一脉相承——"use Grep to find all files ... then Read those files"。性能专家作为子代理同样被指示先跑 git diff 再应用清单(scripts/resolvers/review-army.ts),因此"查 migration 确认索引"属于其职责内的正常操作。
5. 类别三:算法复杂度(Algorithmic Complexity)
四个明确的反模式:
- O(n²) 或更差:集合上的嵌套循环、
Array.find嵌套在Array.map里; - 可哈希化的重复线性搜索:反复线性查找本应使用 hash/map/set 的查找;
- 循环内字符串拼接:应改用
join或StringBuilder; - 重复排序/过滤大集合:一次就够时却做多次。
主清单 review/checklist.md 的 View/Frontend 章节还有一条姊妹规则:"O(nm) lookups in views(Array#find in a loop instead of index_by hash)",且 Fix-First 启发式把 "Inline styles, O(nm) view lookups" 也归入 AUTO-FIX。两条清单共同覆盖了"视图/前端代码里的二次复杂度"这一最常踩的坑。
6. 前端专属类别:包体积与渲染性能
Bundle Size Impact
四个检查点:
- 新增已知重量级的生产依赖:
moment.js、完整引入的lodash、jquery(对应地应选dayjs、lodash-es深导入或按需引入); - 桶导入(barrel imports):
import from 'library'应改为深导入import from 'library/specific',避免把整包拉入依赖图; - 提交了未优化的大静态资源(图片、字体);
- 路由级 chunk 缺少代码分割。
Rendering Performance
五个检查点:
- 请求瀑布(fetch waterfalls):可并行的顺序 API 调用,应使用
Promise.all; - 不稳定引用导致的无谓重渲染:在 render 中生成新的对象/数组字面量;
- 昂贵计算上缺少
React.memo/useMemo/useCallback; - 布局抖动(layout thrashing):循环中交替读取和写入 DOM 属性;
- 首屏以下图片缺少
loading="lazy"。
这两类解释了为什么激活条件是 SCOPE_BACKEND OR SCOPE_FRONTEND 而不是仅后端:包体积与渲染性能只有在前端文件进入 diff 时才适用,而 N+1、索引、分页、阻塞则主要作用于后端。范围信号与清单内容是一一对应的。
7. 类别四与五:缺失分页与异步阻塞
Missing Pagination
- 列表端点返回无界结果(没有 LIMIT、没有分页参数);
- 无 LIMIT 的数据库查询,结果集随数据量线性增长;
- API 响应内嵌完整嵌套对象,而非"返回 ID + 按需展开(expansion)"。
Blocking in Async Contexts
- 异步函数内的同步 I/O(文件读取、子进程、HTTP 请求);
- 基于事件循环的处理器中的
time.sleep()/Thread.sleep(); - CPU 密集型计算阻塞主线程且未 offload 到 worker。
主清单 review/checklist.md 的 "Async/Sync Mixing(Python-specific)" 与之互补,给出更具体的 Python 修复:asyncio.to_thread()、aiofiles、httpx.AsyncClient,以及用 asyncio.sleep() 替换 time.sleep()。两份清单叠加,覆盖了 Node(事件循环 + worker 线程)与 Python(asyncio)两大异步模型。
8. 发现的下游命运:合并、去重、置信度门控与质量分
性能专家的输出并不是终点。Step 4.6 "Collect and merge findings"(review/SKILL.md,模板源见 scripts/resolvers/review-army.ts)定义了完整的消费链:
- 解析:
NO FINDINGS直接跳过;其余按行解析为 JSON,非法行丢弃; - 指纹去重:同 fingerprint 的发现只保留最高置信度者,打上
MULTI-SPECIALIST CONFIRMED (testing + performance)之类的标签,置信度 +1(封顶 10)——如果性能专家与安全专家同时命中同一处,它的可信度会被提升; - 置信度门控:7+ 正常展示;5-6 带 "Medium confidence — verify this is actually an issue" 保留意见;3-4 移入附录;1-2 完全抑制;
- PR 质量分:
quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5)),封顶 10;专家被跳过(小 diff)时记 10.0。test/skill-e2e-review-army.test.ts 的 "Review Army: Quality Score" 用例专门验证了这个公式的可执行性; - 进入 Fix-First:与主 CRITICAL pass 的发现走同一条流水线——机械修复(如补 eager loading)自动应用,模糊的批量询问。带
test_stub的发现强制走 ASK 分支,用户批准后按项目约定落测试文件(RSpec 用spec/、Jest/Vitest 用__tests__/、pytest 用test_前缀、Go 用_test.go后缀); - 持久化:每个专家(含 performance)产出统计对象
{dispatched, findings, critical, informational}或{dispatched: false, reason: scope|gated},随gstack-review-log写入审查日志,供/retro与自适应门控复用。
9. 源码级印证:模板解析器、测试与演进记录
- 单一事实来源:Step 4.5/4.6 的整段文本不是手写在 SKILL.md 里的,而是由 scripts/resolvers/review-army.ts 在文档生成时注入——
generateSpecialistSelection生成分派选择逻辑(含性能专家的SCOPE_BACKEND=true OR SCOPE_FRONTEND=true条件,#L64-L69),generateSpecialistDispatch生成子代理提示词与 JSON schema,generateFindingsMerge生成合并逻辑,generateRedTeam生成红队派遣。/review与/ship共用同一解析器(isShip时步骤号变为 9.1/9.2,见 #L14-L18),/ship因此获得与/review同等深度的专家审查。Codex 宿主下该 resolver 返回空串(ctx.host === 'codex',#L234-L235),即 Review Army 不随 Codex 宿主运行; - 范围检测回归测试:test/diff-scope.test.ts 覆盖前后端识别、ESM/CJS 扩展名(#1810)等边界,保证性能专家的触发条件稳定;
- 端到端行为测试:test/skill-e2e-review-army.test.ts 通过
extractSkillSections从真实 SKILL.md 抽取核心段落构造 fixture 仓库,跑真实代理会话验证 N+1 检出、质量分、JSON schema 合规、多专家共识(同一 SQL 注入被 security 与 testing 同时标记为 MULTI-SPECIALIST CONFIRMED)等; - 版本演进:CHANGELOG.md 的 0.14.4.0 条目记录了 Review Army 的引入——"Every
/reviewnow dispatches specialist subagents in parallel ... Each specialist reads the diff independently with fresh context, outputs structured JSON findings, and the main agent merges, deduplicates, and boosts confidence",以及小 diff(<50 行)跳过专家、大 diff(200+ 行)激活 Red Team 的设计取舍。
10. 实战使用指南
在装有 gstack 的项目中实际触发性能专家审查的方式:
- 常规路径:在功能分支上直接说 "review this PR" / "code review"(review/SKILL.md 的 triggers),当 diff ≥50 行且含后端或前端文件时,输出会包含 "Dispatching N specialists: [...]" 一行,说明 performance 被选中或被门控/跳过("Gated: [names] (0 findings in N+ reviews)");
- 强制运行:提示中加入
--performance(如 "review this PR --performance"),无视自适应门控强制派遣;--all-specialists则全量强制; - 预期输出形态:
SPECIALIST REVIEW: N findings (X critical, Y informational) from Z specialists,每条发现形如[CRITICAL] (confidence: 9/10, specialist: performance) app/models/post.rb:42 — N+1: ...,附带 Fix 建议与 PR Quality Score; - 阅读提示:置信度 5-6 的发现带 "Medium confidence" 保留意见,属于"值得看一眼但不必照单全收";带 test_stub 的 ASK 项展示了拟创建的测试骨架,批准即落盘。
11. 小结
review/specialists/performance.md 用不到 60 行文本定义了一个高信息密度的机器契约:以 SCOPE_BACKEND OR SCOPE_FRONTEND 为触发条件,以"每行一个 JSON、无发现只说 NO FINDINGS"为输出纪律,以七类性能反模式为审查本体。仓库源码进一步展示了它如何被 scripts/resolvers/review-army.ts 注入 /review 与 /ship、如何经 bin/gstack-diff-scope 的范围信号门控、如何在 Step 4.6 中与六个兄弟专家的发现做指纹级去重与置信度提升,并由 test/skill-e2e-review-army.test.ts 与 test/fixtures/review-army-n-plus-one.rb 这类端到端用例持续验证。对使用者而言,理解这套机制的价值在于:性能问题不再是"审查者心情好坏"的产物,而是有确定触发条件、确定输出 schema、确定下游处置路径的工程化检查——N+1 这类机械问题被自动修掉,模糊问题带着证据和测试骨架交还给人判断。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00