# Anthropic 一句話否決的 900 行 PR：效能最佳化要付多少維護成本

> 每次送出省 2 ms 的 900 行 PR，換來一個要維護的 build plugin。先定義量測起點與終點、把行為條件寫進 AI 任務，再決定效能改動值不值得留下。

原文：https://sheng.page/posts/performance-gain-vs-maintenance-cost/ · 發布：2026-11-09 · 作者：Sheng

Anthropic 在 2026 年 9 月 23 日的工程文章〈How we made claude.ai 3x faster in two weeks〉裡提到一份約 900 行的 PR，能讓每次送出訊息少花約 2 ms，代價是多一個 build plugin 要維護。原文引了否決時的那句回覆：「going to gavel that 2ms per send is not worth the complexity of maintaining this build plugin.」意思是每次送出省下的 2 ms，抵不過維護這個 build plugin 的複雜度。原文沒有交代這個 2 ms 怎麼量，也看不出有沒有人質疑數字，只確定這句回覆認為維護成本不值得。

效能改動合併前，得先確認量到的改善，再算留下來要付多少維護成本。這句回覆寫在原文談工作調度的那段，團隊同時開著超過 150 條範圍很窄的 Slack thread（討論串），各自盯一個 benchmark 或一段使用流程找改進，工程師則決定先做哪裡，遇到 thread 互相干擾或收益漸少時，再調整或結束工作。AI 可以短時間交出好幾個改動，原本排不進時程的最佳化也有機會先量過；合併之後，相容性和後續修改仍是團隊的工作。原文沒說那份 PR 是誰寫的，收不收仍要另看維護成本。

## 3.1 秒到 0.55 秒量的是什麼

同一篇文章說，全新載入 claude.ai 之後，頁面可以開始輸入的 p75 時間從 3.1 秒降到 0.55 秒。這量的是頁面何時能打字，那時訊息還沒送出，模型回答多快不在這個數字裡。其他操作各量各的：Claude Code 開新 session 從 0.8 秒降到 0.3 秒，Cowork 雲端 session 載入從 2.6 秒降到 0.73 秒，幅度都不一樣。

Google 定 Core Web Vitals 門檻時選 75th percentile，要多數造訪達標，同時不讓少數極端值把結果拉歪。p75 改善只表示四分之三的載入落在這個時間內，其餘仍可能更慢；p95、p99 有沒有一起改善，原文沒有給，也不能從 p75 推出來。

## 先定義量測的起點與終點

開始修改前，先把「網站很慢」縮小到一次操作。畫面出現和輸入框能打字是不同時間點；按下送出後，等待可能發生在請求送出前、後端處理中，或資料回來之後。Anthropic 的做法是先補量測，直到每個基準都能互相比較：每次量測從使用者互動開始，到結果渲染完成為止，並分開 client 和伺服器各花了多少時間。

假設後端少花了 100 ms，前端仍要多等 500 ms，整段操作還是要等，使用者能不能感覺到改善，要看整段耗時怎麼變。反過來，讓輸入框提早能打字，也要確認使用者在那段時間打的字能完整送出，否則量到的數字變好，體驗反而變差。

## 測試條件跟數字一起留下來

固定資料量和同時請求數，記下測試裝置，冷快取和熱快取的結果分開看，修改前後用同一套可重跑的測試，才知道差異來自程式，還是條件換了。Anthropic 文中一張圖標示了 4 vCPU container、headless Chrome、不限制 CPU、每個數值跑 2 到 3 次，這麼少的次數看得出很大的差異，變異有多寬就估不準。純 JS 的 hot path 則用 Valgrind 搭配 `node --predictable` 數指令，跟 commit 進 repo 的基準比較，再和實際耗時（wall-clock time）互相對照；原文的說法是「one run, no statistics needed」，意思是在他們固定的測試條件下，指令數可以重現，不必靠多次取樣估計。

上線後還要看實際流量，確認改善出現在原本要解決的操作上。他們比較的是 8 月 13 日與 8 月 27 日的真實使用者 p75，兩天都是星期四，依平台和產品分開看。每次部署後，再依 build 和平台讀實際使用者的資料。比較時要留意，兩天量到的未必是同一群使用者，裝置和地區的比例變化也可能影響 p75，不能把差異都算到程式改動上；兩天之間合併了 3,000 多個變更，單看這兩個數字，也分不出哪一個改動貢獻多少。如果只有測試環境變快，就回頭找環境差異，不能因為程式已經寫完就放寬驗收標準。

上線方式也影響能不能退回：他們把近 200 個 feature flag 分成 kill switch 和逐步開放兩類，高風險改動先給員工，再給 1% 使用者，最後才全部開放，照原文的說法，3,000 多個變更合併後，沒有使用者看得到的事故，也沒有 rollback。

## 把行為條件寫進 AI 的任務

以快取為例，查詢時間可能立刻下降，但資料更新後多久讀得到新值、權限改了以後舊結果是否還讀得到、快取失效或後端故障時請求怎麼走，都要一起驗證。

這些條件應該在交給 AI 之前寫進任務。沒寫的話，就算它交回更快的 benchmark，團隊還是得自己確認原有行為，補查權限與錯誤回應。任務可以先限定一條查詢路徑，保留修改前的數字，把權限檢查和錯誤回應的測試補齊，再比較修改後的延遲和資源消耗。一次只動一條查詢路徑，改善從哪裡來才分得清，不對就整段退回。Anthropic 每條 thread 只盯一個 benchmark 或一段流程，範圍也是這樣切。

## 合併前算維護成本，採用後留成回歸檢查

合併前先算維護成本。Knuth 1974 年談過早最佳化時，也提醒讀者別放過關鍵的 3% 機會。原文對這份 PR 的否決理由著重在維護成本，改動越容易產出，這筆合併後的成本越容易漏算。刪掉不必要的查詢，往後少一段工作，也少一層要讀的程式；新增一層快取，還要搭配背景工作同步資料，往後除錯就得多查幾個地方。2 ms 在高頻率的迴圈裡可能很重要，偶爾執行一次的操作收益就小得多，依 Amdahl's law，局部加速對整體的幫助，受限於那段原本占了多少時間。原文沒給每次送出的總耗時，2 ms 占多少比例，外人算不出來。

Anthropic 把兩條路徑的指令數改善寫成 CI 的 ratchet（只能往下調的門檻）：之後任何讓這兩條路徑指令數上升的 PR 都會讓 CI 失敗，每天另有一個 job 在指令數下降時把上限往下調。這種不留容差、只准下修的規則，用在有雜訊的實際耗時上容易誤判：上限追著每天的最小值走，最小值落在雜訊偏低的那一側，之後沒改任何東西的 PR 大多也會超過上限。下面用模擬試一下：程式完全不變，只改雜訊大小。模擬假設雜訊彼此獨立、門檻沒有容差，每日 job 把上限下修到當天所有 PR 量到的最小值：

```python
import random

def false_fail_rate(noise_sd: float, days: int = 60, prs_per_day: int = 10) -> float:
    rng = random.Random(20261104)
    true_cost = 1000.0                      # 程式沒變，真實成本固定
    ceiling = true_cost + rng.gauss(0, noise_sd)
    fails = 0
    for _ in range(days):
        today = [true_cost + rng.gauss(0, noise_sd) for _ in range(prs_per_day)]
        fails += sum(x > ceiling for x in today)
        ceiling = min(ceiling, min(today))  # 每日 job：量到更低就下修上限
    return fails / (days * prs_per_day)

for sd in (0.0, 5.0, 20.0):
    print(f"noise_sd={sd:>4}: {false_fail_rate(sd):.1%} 的 PR 被誤判變慢")
```

```text
noise_sd= 0.0: 0.0% 的 PR 被誤判變慢
noise_sd= 5.0: 98.7% 的 PR 被誤判變慢
noise_sd=20.0: 98.7% 的 PR 被誤判變慢
```

在這個模型裡，雜訊只要不是零，誤判率就接近百分之百，通過的只有低於或等於當天門檻的少數讀值，而且跟雜訊大小無關：上限追著最小值走，比的只是排序，雜訊放大幾倍，排序都一樣。這個結果只適用於上面的模型，實際 CI 的誤判率仍取決於取樣方式和門檻規則。實際耗時要當門檻，得像〈[Monte Carlo 測試的容差怎麼定：標準誤、誤拒率與位元重現](/posts/monte-carlo-test-tolerance-and-false-rejection/)〉那樣先估變異、留容差，基準也不能每次都追著最低值下修，代價是小幅退步會被放過。

下一次讓 AI 改效能，可以先挑一條使用者常抱怨的流程，建立基準並補好行為測試，採用之後就把量測留成回歸檢查。若使用指令數，先確認它能反映實際耗時，後續同一個操作變慢時，團隊才有資料可以追。

## 補充筆記

- Anthropic 用一句話否決了每次送出省 2 ms 的 900 行 PR，理由是省下的時間抵不過維護這個 build plugin 的複雜度。
- 3.1 秒到 0.55 秒是全新載入到頁面可以輸入的 p75，不代表模型回答速度，也看不出 p95、p99 的變化。
- 先定義一次操作的起點與終點，分開 client 和伺服器的時間，才知道該改哪一段。
- 兩個日期的 p75 未必來自同一群使用者，流量組成也可能影響數字，要依平台和產品分開看。
- 交給 AI 之前先寫明行為條件，例如快取的資料新鮮度、權限與故障處理。
- 2 ms 值不值得要看它占整體多少比例；CI ratchet 若用實際耗時，又不留容差、只追最低值，就容易把未變慢的 PR 判成退步。

## 延伸閱讀

- Anthropic〈[How we made claude.ai 3x faster in two weeks](https://claude.dev/blog/how-we-made-claude-ai-faster/)〉：本文的案例來源，900 行 PR 的回覆、p75 數字、量測定義、CI ratchet 與上線方式都出自這篇，是該公司自己的說法。
- web.dev〈[How the Core Web Vitals metrics thresholds were defined](https://web.dev/articles/defining-core-web-vitals-thresholds)〉：Google 的官方方法說明，解釋為什麼用 75th percentile 評估網頁效能。
- Wikipedia〈[Program optimization](https://en.wikipedia.org/wiki/Program_optimization)〉：百科條目的二手整理，收錄 Knuth 1974 年談過早最佳化的引文，也討論手動最佳化的維護成本。
- Wikipedia〈[Amdahl's law](https://en.wikipedia.org/wiki/Amdahl%27s_law)〉：百科條目的二手整理，說明局部改善對整體速度的限制。

> 想法與技術判斷出自 Sheng，和 Claude 一起起草 · 案例數字取自 Anthropic 2026-09-23 原文，模擬在 Python 3.13.18 實跑。