Claude Code 揾效能瓶頸:先量度、後收窄、再修正三段式 workflow
頁面載入慢、API 慢、build time 暴增。一開口就叫 Claude「優化」,佢會出一堆零碎小改動。教你三段式 workflow:先 profile 攞到可量度嘅數據、收窄揾出最痛嗰三個位、一次只修一個再重新量度。冇量度過就唔好優化。
情境
你嘅 HK e-commerce site,舊年 LCP 1.8 秒,而家 4.2 秒。或者你個 SaaS API,上個月 p95 180ms,而家 950ms。又或者 pnpm build 由 45 秒變 3 分鐘。
你做嘅第一件事,多數係咁:
- 開 Claude Code,貼個 component / endpoint 嘅 code 落去
- 打:「優化呢段 code,太慢」
- Claude 出咗 5 個建議:useMemo 包多幾個 value、加 lazy load、改 SELECT 用 index、拆細 bundle…
- 你全部用晒,重新部署。LCP 由 4.2 秒變 4.1 秒。
- 你問:「仲有冇得優化?」Claude 再出 5 個。又用晒。3.95 秒。
- 過咗 3 個鐘,砌咗 50 個零碎小改動,p95 先改善 12%。你開始懷疑人生。
呢個係典型 AI 輔助優化嘅反面教材:
- 你冇 profile 數據,Claude 唯有喺呢個 codebase 入面撩啲「教科書 hotspot」嚟改
- 真正嗰個食咗 80% 時間嘅瓶頸,其實喺你冇貼出嚟嗰個 file 入面(可能係一個 N+1 query、一段阻塞主執行緒嘅 JS、一個同步嘅 filesystem read)
- 你最後做咗 50 個各改善 5% 嘅小修補,但嗰個佔 95% 嘅元兇仍然喺度
紀律:冇量度過就唔好優化。下面三段式 workflow 係先量度、後收窄、再修正,每段都有條死規矩。
跟住做
1. 揀啱量度工具(10 分鐘)
落手前,揀啱 profiler。冇 profiler 就唔好開始。
| 場景 | Tool | 量度乜 |
|---|---|---|
| 前端 page load | Chrome DevTools Performance + Lighthouse | LCP / INP / CLS / TBT |
| 前端 runtime(React render) | React DevTools Profiler | Component render time |
| Backend API (Node) | clinic.js / 0x / built-in --inspect | Flame graph、event loop lag |
| Backend API (Python) | py-spy record | CPU sampling |
| Backend API (Go) | pprof | CPU + heap |
| Database query | Postgres EXPLAIN ANALYZE / slow query log | Query plan + time |
| Build time | vite --profile / webpack-bundle-analyzer / esbuild --metafile | Plugin / chunk cost |
⚠️ 唔好估邊樣慢。你以為「實係 React render」,profile 出嚟可能係個 500KB 嘅 lottie animation 阻塞咗主執行緒。
2. 擷取 profile,再餵畀 Claude 分析(15 分鐘)
跑 profiler,將原始輸出儲落 file(唔好淨係睇 GUI screenshot)。
# 例:Node API 用 clinic flame
npx clinic flame -- node server.js
# 跑 load test 5 分鐘
# Stop → 出 flame.html + .clinic-flame folder
# 例:Python 用 py-spy
py-spy record -o profile.svg -- python app.py
# 跑 typical workload
# 例:Postgres slow query
# 已開 log_min_duration_statement = 100ms
tail -n 500 /var/log/postgresql/postgresql.log > slow.log
然後喺 Claude Code 入面,唔好貼 code,先貼個 profile:
我喺度 debug 一個效能 regression。徵狀:[頁面 LCP 4.2 秒 / API p95 950ms / build 3 分鐘]。我啱啱跑咗 [clinic flame / py-spy / Lighthouse / EXPLAIN ANALYZE],原始輸出喺 (已附上)。
呢個 prompt 嘅紀律:禁止 Claude 跳去修 code。先量度、後收窄、再修正三段,每段唔好溝埋一齊。
3. 收窄到頭號元兇,形成假設(15 分鐘)
Claude 揾到頭號元兇(譬如:getOrdersWithItems() 食咗 67% wall time),下一步唔係修,係確認 root cause。
開個跟進 prompt,餵嗰個特定 file:
Profile 顯示 getOrdersWithItems()(src/api/orders.ts,已附上)食咗 67% wall time。我用 Postgres slow log 睇到呢個 endpoint 每個 request 出 51 個 query。請: 逐行讀晒 getOrdersWithItems 講邊一行造成 51 個 query(精確到行號) 分類:呢個係 N+1、定重複抓取、定缺 index? 你呢個假設嘅信心:高 / 中 / 低?如果中或低,建議再擷取乜嘢數據去確認。 仍然唔好提議點改 code,淨係確認原因。
呢一步嘅意義:你逼 Claude 做診斷,而唔係開藥。診斷錯,開咩藥都一定錯。
4. 一次修一個,修完即刻重新量度(20 分鐘)
確認咗 root cause 先入修正階段。一次只修一個元兇,修完即刻重新量度。
# Cadence 流程
1. Baseline measurement → 寫低(e.g. p95 = 950ms)
2. Fix top-1 culprit
3. Re-measure → 寫低(e.g. p95 = 220ms)
4. Δ = 730ms = 77% improvement → 真係嗰個 culprit
5. 仲未達標?返 step 1,攞新 profile,揾新 top-1
已確認:getOrdersWithItems 第 42-58 行係 N+1(喺每個 order 嘅迴圈入面再 query items)。請提議一個修正: 改 query 用 JOIN 一次過攞,或者用 WHERE order_id IN (...) 批量抓取 出 diff,淨係改呢個 function 列出可能會搞壞嘅情況(譬如空嘅 order list、order 數量過 1000、items table 有 soft delete) 列出我應該加邊個 test case 去防 regression 唔好順手執埋其他嘢。我要一個孤立嘅 diff,方便重新量度。
紀律:一個 PR 一個修正。你溝埋兩個修正,重新量度嗰個 Δ 就唔知邊個貢獻幾多。下次 regression 你又冇得逐個 bisect 揾返。
變化
變化 1:前端頁面載入(Web Vitals + Lighthouse)
HK e-commerce 最常見:手機 4G LCP 5 秒。流程:
- 量度:Chrome DevTools → Lighthouse(Mobile,Slow 4G throttle),儲存個 JSON report
- 收窄:餵個 JSON 落 Claude,問「最拖累 LCP 嘅頭三樣嘢」。通常會講出阻塞渲染嘅 JS / 大 image / 第三方 script
- 確認假設:對最大嗰個譬如「Hero image 1.8MB」,問 Claude「
<Image>component 點設定先用到 next-gen format 同 responsive sizes」 - 修正再重新量度:一次只改一樣(譬如轉 AVIF),重新跑 Lighthouse,寫低 Δ
⚠️ 唔好喺同一個 PR 入面又轉 image format、又 lazy load script、又 inline critical CSS——你重新量度嗰刻分唔到邊個有效。
變化 2:後端 API(database / N+1)
SaaS dashboard 開頁 7 秒,p95 950ms。先跑 EXPLAIN ANALYZE:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE workspace_id = $1;
-- 留意 Seq Scan vs Index Scan、Rows Removed by Filter、loops
餵個 plan 落 Claude,問「呢個 plan 邊度唔合理」。Claude 通常即刻撠到:缺 index、SELECT * 拉太多 column、subquery 應該改寫成 CTE。
關鍵:唔好淨係問「優化呢個 query」。要問「呢個 EXPLAIN plan 邊行成本最高,點解」。前者係叫 Claude 估,後者係叫 Claude 讀數據。
N+1 嘅特徵:log 入面一個 100ms 嘅 request 對住 50 個形狀一模一樣嘅 query。Prisma / ActiveRecord / SQLAlchemy 都各自有 eager-load 嘅寫法(include / includes / selectinload),叫 Claude 直接出返嗰個 framework 慣用嘅寫法。
變化 3:Build time(webpack / vite bundle analyzer)
pnpm build 由 45 秒變 3 分鐘,CI cost 飛升。
# Vite
vite build --profile # 出 profile JSON
npx vite-bundle-visualizer # 視覺化 chunk
# Webpack
webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json
餵 JSON 落 Claude,問「邊個 plugin / 邊個 dependency 食咗最多 build time」。通常元兇係:
- 升級某個 dependency 之後,SSR bundle 入面打包咗一個本嚟唔需要嘅大 package(lodash 全套、moment 全部 locales)
@svgr/babel-plugin-styled-components之類嘅 transform 每個 file 都要跑- TypeScript
incremental: false,或者冇設定tsBuildInfoFile
修完即刻再跑一次 time pnpm build,記低 Δ。CI 加咗 cache key 之後再記多次冷啟動同熱啟動嘅分別。
拆解:點解 work,同邊度會仆街
跟到上面就已經夠用。下面呢段係畀**想由「跑一次 profile 揾到嘢」做到「次次量度都信得過」**嘅人——初學者可以跳過,唔影響你跟住做。
效能調優最容易呃自己嘅地方係:個數字郁咗,唔代表你真係改善咗件事。你以為收窄到元兇,其實量度本身就已經呃緊你。呢個 workflow 實際會喺呢幾個位爆,你要預咗:
1. Profiler 自己拖慢咗你個程式(observer effect) 好多 profiler 用 instrumentation(喺每個 function 入口插 hook)去計時,呢樣本身有開銷。開咗 profiler 嗰刻,啲 function call 多嘅細 function 會被放大到睇落好似好食時間,但實際生產環境根本唔係咁。
- 會出事:你照住 profile 排名去修,結果修咗一個喺真實環境其實唔慢嘅位。
- 點救:盡量用 sampling profiler(
py-spy、pprof、Chrome 嘅 sampling mode)而唔係 instrumentation;睇 wall-clock 排名之餘留意絕對毫秒數,唔好淨睇百分比。喺 prompt 講明你用緊邊種 profiler,Claude 解讀 flame graph 嗰陣會慎重啲。
2. 你量度嗰個 workload 唔代表真實流量 你喺 dev 機跑五分鐘 load test,但個 N+1 喺資料量細嗰陣可能完全唔覺。生產有十萬條 order,dev 得五十條,個 query plan 連 Postgres 揀唔揀 index 都可能唔同。
- 會出事:你喺 dev 量度話「修好咗」,上到生產又慢返,因為 baseline 根本量錯咗環境。
- 點救:量度要用貼近生產嘅資料量同 cache 狀態(凍 cache vs 熱 cache 要分開講)。
EXPLAIN ANALYZE喺有代表性嘅資料上跑,唔好用空表。Claude 唔知你個 dev 環境同生產差幾遠,呢個 context 要你親手補。
3. Re-measure 個 Δ 其實係雜訊 你修完一個位,p95 由 950ms 跌到 920ms,你開心咗。但你只跑咗一次,背景有個 cron job、有 GC、有鄰居 VM 搶 CPU——呢 30ms 可能純粹係 run-to-run variance。
- 會出事:你以為某個修正有效,commit 咗,但其實個 Δ 喺誤差範圍以內,下次量度又彈返上去。
- 點救:每個量度跑多幾次取中位數,唔好淨信一 run。改善要夠大、夠穩定先當數(譬如三 run 都係 77% 先信,唔係偶然一次)。叫 Claude 出修正嗰陣,順手問佢「呢個改動理論上應該慳幾多」,同實測對一對,差太遠就有古怪。
4. Claude 睇嘅 profile 係截斷版,唔係全貌
你 tail -n 500 條 slow log,或者貼咗半個 flame graph 落去——Claude 只睇到你畀佢嗰嚿。佢唔會知道你剪走咗嘅嗰半係咪藏住真正元兇,但佢答得好肯定。
- 會出事:Claude 喺一個被你截斷嘅 profile 上,自信咁指錯元兇,你又信咗去修。
- 點救:餵之前自己確認最熱嗰幾條真係喺你貼出嚟嗰段入面;如果係 sampling 出嚟嘅 svg / json,盡量畀完整 file,唔好淨貼一截文字摘要。Claude 講「呢條最熱」嗰陣,反問佢「你係咪只睇到我貼嘅部分,有冇可能真正瓶頸喺截走咗嗰段」。
5. 修一個瓶頸,會令下一個瓶頸浮出嚟(Amdahl 陷阱) 你修好咗食 67% 嗰個 N+1,p95 跌一大截,正常。但跌完之後,原本佔 10% 嘅另一個位,而家可能變咗佔 40%——瓶頸搬咗位,唔係冇咗。
- 會出事:你以為一個 PR 搞掂,收工;過兩日又有人投訴慢,因為新嘅頭號元兇你冇再 profile。
- 點救:每修完一個,重新 profile 一次(唔係淨重新量總時間),睇新嘅 top-1 喺邊。呢個正正係上面「返 step 1 攞新 profile」嗰步嘅理由——唔好跳。
呢幾個位,就係「跑一次 profile 揾到嘢」同「次次量度都信得過、改善真係落到生產」之間嘅距離。
一個紀律
冇量度過就唔好優化。冇收窄到就唔好修。
Claude 唔係慢——係你冇畀 profile 佢睇,佢唯有撩 codebase 入面啲「教科書 hotspot」。呢類 hotspot 通常只改善 3-5%,但就耗你 3 個鐘。真正嗰個食咗 80% 嘅瓶頸,profile 一跑就睇到,收窄 5 分鐘就確認到。
你嘅工作:
- 量度(揀啱 profiler,將原始輸出儲低)
- 管住 Claude(一段一個 prompt,禁止跳階段)
- 每修一個就重新量度一次(保留得返功勞歸邊個)
Claude 嘅工作:
- 讀 profile(flame、EXPLAIN plan、Lighthouse JSON,呢啲佢真係叻)
- 推測 root cause(N+1 / IO 阻塞 / bundle 過大)
- 產出孤立嘅 diff(一次一個,唔順手執其他嘢)
呢個分工做得好,效能 regression 由「3 個鐘 50 個小修補」變「30 分鐘 1 個 PR」。做唔好,你就會喺 useMemo 同 SELECT column 之間兜圈。
下次撞到「個 app 變慢」,收手 10 分鐘:揀 profiler、儲存個原始輸出、再開 Claude。先量度、後收窄、再修正三段,紀律守得住,瓶頸自己就會浮返出嚟。
文中工具 · 連結
- Claude Code CLI· 付費
開發者用 — terminal 入面同 Claude pair coding
睇完想同 Claude 一齊行一次?
撳一撳,就將成段 tutor 指示(連埋成篇文嘅內容)抄入剪貼簿。 貼入 Claude.ai 或 Claude Desktop,佢會用廣東話帶你一步一步行, 每步問你填關鍵位,最後畀返一個專為你情況寫嘅 prompt 帶走。
- 創作者 · 50 分鐘
Claude Code 揾卡咗 4 個鐘嘅 bug:log 倒推、git bisect、minimal repro 三個 pattern
撞到一個 bug 卡足 4 個鐘,直接叫 Claude「fix」通常出個亂作嘅 patch。教你 3 個有紀律嘅 debugging pattern:log 倒推、git bisect 配 AI 寫 predicate、minimal repro。香港 solo dev 同細 team 實戰角度。
- 創作者 · 60 分鐘
Claude Code 做 safe refactor:先 characterization test、後 mechanical move、再核對等價性
Claude Code 做 safe refactor 嘅三步流程:先 characterization test 釘住現有行為、再 mechanical move 唔改邏輯、最後核對等價性。教你避開 AI 直接重寫撞爛 edge case 嘅死位。
- 創作者 · 30 分鐘
Claude Code 由零安裝:Mac / Linux 30 分鐘起第一個 project
你睇 Twitter / HN 講 Claude Code,但搜尋「install」出咗 5 個矛盾教學,唔知由邊度開始。呢篇 30 分鐘有系統咁裝好 —— Mac / Linux 設定、API key、第一個 project 跑起、權限設定、常見安裝錯誤拆解。