客服連續三天轉來同一句話:「商品頁好慢」。老闆要她這週讓網站變快,她正盯著一大坨程式碼,猜該從哪裡下手。
Anthropic 的工程團隊發現,claude.ai 網站和 Claude 桌面應用程式被使用者嫌慢,而且他們坦承「使用者說得對」。於是在 8 月做了一次為期兩週的衝刺sprint,把人力集中在一個明確目標上、限時完成的工作方式,把核心操作的速度提升了約 3 倍。整場衝刺都在同一個 Slack 頻道裡進行,Claude 出現在每一個討論串中。
整件事的關鍵句就是文章標題:只要 Claude 能量測某件事,它就能把那件事變快。所以團隊第一步不是改程式,而是讓 Claude 先讀遙測資料App 在使用者裝置與伺服器上自動回報的使用紀錄與耗時數字,挑出佔 95% 使用者活動的四條使用者旅程使用者為了完成一件事而經歷的一連串操作,例如從打開 App 到能開始打字:啟動 App、開始一場對話、載入既有對話、送出訊息。網頁版與桌面版、各產品加起來,共拆成 13 個量測項目。
為了讓這 13 個數字能互相比較,團隊補上了埋點在程式裡插入計時與記錄的程式碼,讓每次操作花了多久都留下數字,規定每個量測都從「使用者動作」開始、到「結果畫面顯示完成」才結束,並把「使用者裝置上花的時間」和「伺服器上花的時間」分開算。成果用 p75把所有人的等待時間由短排到長,第 75% 位置的那個數字;代表四個人裡有三個人等待時間不超過它 表示:新開 claude.ai 到「可以打字」從 3.1 秒降到 0.55 秒;開一個新的 Claude Code 工作階段從 0.8 秒降到 0.3 秒;載入 Claude Cowork 雲端工作階段從 2.6 秒降到 0.73 秒。
在這套流程裡,Claude 扮演的是代理人不只回答問題,還會自己規劃步驟、動手做事、檢查結果的 AI:找瓶頸、寫基準測試、送出改進、盯著每一次上線。人類只做三件事:設定目標、決定取捨、批准每一項改動。最後團隊合併了超過三千項改動,沒有任何一次造成客戶端事故,也沒有任何一次需要回滾上線後發現有問題,把程式退回上一個穩定版本。
| 面向 | 憑感覺優化 | 量測驅動(Anthropic 的做法) |
|---|---|---|
| 起點 | 聽到「好慢」就猜哪裡慢 | 先讓 AI 分析用量資料,選出佔 95% 活動的四條旅程 |
| 目標 | 「盡量快一點」 | 13 個量測各有目標,由專案的毫秒估算加總而來 |
| 判斷進度 | 靠體感 | 看 p75 前後對比,例如 3.1 秒到 0.55 秒 |
| 上線之後 | 上線就不管了 | Claude 監看每一次部署有沒有退步 |
| 人的角色 | 親手改每一行 | 設定目標、做取捨、批准每一項改動 |
這是本課下載專案 guard.py 的核心,把文章「先量測、再比較、再把關」的精神縮成幾行。sample_ms 與 percentile 定義在 measure.py,等一下實作時會看到。
samples = sample_ms(fn, runs=30)p75 = percentile(samples, 75)base = json.loads(BASELINE.read_text(encoding='utf-8'))['p75_ms']ratio = p75 / baseif ratio > LIMIT: print('退步!這次改動讓它變慢了,擋下。') return 1return 0這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。
import time
def sample_ms(fn, runs=30):
# 先暖機一次:第一次執行常有載入與快取的雜訊,會污染基準線
fn()
out = []
for _ in range(runs):
t0 = time.perf_counter()
fn()
out.append((time.perf_counter() - t0) * 1000)
return out
def percentile(samples, p):
# 由快排到慢再取位置;p75 的意思是 4 次裡有 3 次不會比它慢
s = sorted(samples)
k = round(p / 100 * (len(s) - 1))
return s[k]
def report(name, samples):
avg = sum(samples) / len(samples)
p50 = percentile(samples, 50)
p75 = percentile(samples, 75)
p95 = percentile(samples, 95)
print(f'{name}:平均 {avg:.2f} ms|p50 {p50:.2f}|p75 {p75:.2f}|p95 {p95:.2f}')
import random
from measure import report, sample_ms
random.seed(7) # 固定亂數種子:每次跑的資料一樣,優化前後才比得公平
ORDERS = [{'id': i, 'customer': random.randrange(2000)} for i in range(20000)]
WANTED = list(range(0, 2000, 20)) # 要查的 100 位客戶
def slow():
# 每位客戶都把 2 萬筆訂單從頭掃一遍:工作量 = 客戶數 x 訂單數
return {c: [o for o in ORDERS if o['customer'] == c] for c in WANTED}
def fast():
# 只掃一次訂單、建好索引(字典),之後每位客戶直接查表
index = {}
for o in ORDERS:
index.setdefault(o['customer'], []).append(o)
return {c: index.get(c, []) for c in WANTED}
if __name__ == '__main__':
# 優化前後結果必須一致,否則「更快」沒有意義
assert slow() == fast()
report('優化前', sample_ms(slow, runs=15))
report('優化後', sample_ms(fast, runs=15))
import json
import sys
from pathlib import Path
import demo
from measure import percentile, sample_ms
BASELINE = Path('baseline.json')
# 小函式的量測本來就會上下抖動;設太緊會天天誤報,設太鬆又抓不到真退步
LIMIT = 1.5
def main(mode, simulate_regression):
# 用 slow 模擬「有人把程式改壞了」,親眼看守門員擋下它
fn = demo.slow if simulate_regression else demo.fast
samples = sample_ms(fn, runs=30)
p75 = percentile(samples, 75)
if mode == 'save':
BASELINE.write_text(json.dumps({'p75_ms': p75}), encoding='utf-8')
print(f'已存下基準線:p75 = {p75:.2f} ms')
return 0
if not BASELINE.exists():
print('找不到 baseline.json,請先執行:python guard.py save')
return 2
base = json.loads(BASELINE.read_text(encoding='utf-8'))['p75_ms']
ratio = p75 / base
print(f'基準 {base:.2f} ms → 現在 {p75:.2f} ms({ratio:.2f} 倍)')
if ratio > LIMIT:
print('退步!這次改動讓它變慢了,擋下。')
return 1
print('通過:沒有退步。')
return 0
if __name__ == '__main__':
mode = sys.argv[1] if len(sys.argv) > 1 else 'check'
sys.exit(main(mode, 'slow' in sys.argv))