本日核心價值 (Core Focus): 把 CPU、RSS、p95 latency、slow query 與一小段熱點程式碼組成「可驗證假設清單」,讓 ChatGPT 協助縮小 Memory Leak 與 I/O 瓶頸範圍;AI 負責提出量測計畫,profiler 與
EXPLAIN ANALYZE負責給證據。
概念說明與實戰情境 (Overview)
線上服務變慢時,最容易失敗的做法是把整支 service 貼進 ChatGPT,請它「找 Memory Leak」。沒有 RSS 曲線、p95 latency、GC 次數或 slow query,模型只能依常見反模式猜答案,接著建議一次大重寫。正確工作流相反:先固定觀測窗口與指標,再截熱點函式(通常 30–80 行),要求模型產出假設、對應證據、以及「還沒量到之前禁止改架構」。ChatGPT 擅長把指標異常對齊到 IDisposable、未關閉檔案、無界快取、N+1 Query 這類模式;它不能取代 dotMemory、py-spy 或資料庫的 EXPLAIN ANALYZE。
關鍵操作與範例 (Implementation & Example)
把效能調優拆成四步,每一步都要留下數字,而不是感覺。
最小指標組可直接當 Prompt 的輸入表:
| 指標 | 為什麼要量 | 常見對應方向 |
|---|---|---|
| CPU | 區分計算熱點與等待 I/O | 忙迴圈、序列化、壓縮 |
| RSS / Working Set | 區分成長型 leak 與短暫峰值 | 未釋放、無界快取、大物件 |
| p95 / p99 latency | 使用者體感,不是平均值 | lock、同步 I/O、下游逾時 |
| Slow query / DB time | 把應用層與資料庫切開 | 缺索引、N+1、全表掃描 |
| GC / allocations(若有) | 驗證「看起來像 leak」是否只是分配過多 | 短命物件、boxing、大 List |
以下 Prompt 強制模型先要證據。可換成團隊現用的 ChatGPT API 模型;重點是輸出契約,不是模型品牌。
你是效能除錯助理,不是重構機器人。
輸入:
- 觀測窗口:{window}
- 指標:CPU={cpu} RSS={rss_trend} p95={p95} slow_query={slow_sql}
- 熱點程式碼(僅此一段,禁止假設其他檔案存在):
{code}
規則:
1. 先列出 3–5 條假設。每條必須包含:可能原因、會影響的指標、下一步要量什麼、如何否證。
2. 若指標不足以支持「Memory Leak」這個詞,必須改寫成「尚未證實的記憶體成長」,並說明缺哪筆證據。
3. 在沒有 profiler / EXPLAIN ANALYZE / 複現步驟前,禁止建議重寫模組、換 ORM、或導入新快取框架。
4. 若程式碼出現 IDisposable、HttpClient、檔案 handle、靜態 List/Dictionary,只能標成「高優先驗證點」,不能直接當結論。
5. 輸出 JSON:hypotheses[], required_measurements[], safe_next_command[]。
C# 常見的假陽性是「看到 new HttpClient() 就判定 leak」。HttpClient 實作 IDisposable,但每個 request 都 using 掉,容易造成 socket 耗盡;完全不釋放又會讓連線與 handler 累積。正確方向是 IHttpClientFactory(或受控的長生命週期實例),而不是「全部加 using」或「全部不要 Dispose」。
// 反模式 A:每個 request new HttpClient,且不釋放
public async Task<string> FetchAsync(string url)
{
var client = new HttpClient(); // handler / socket 可能累積
return await client.GetStringAsync(url);
}
// 反模式 B:每個 request using HttpClient —— 看起來很負責,卻可能打爆 sockets
public async Task<string> FetchWrongDisposeAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
// 正途:由 factory 管理 handler 生命週期
public sealed class PriceClient
{
private readonly HttpClient _http;
public PriceClient(HttpClient http) => _http = http;
public Task<string> FetchAsync(string url) => _http.GetStringAsync(url);
}
// Program.cs
builder.Services.AddHttpClient<PriceClient>(c =>
{
c.Timeout = TimeSpan.FromSeconds(5);
});
把上面兩段反模式連同「RSS 30 分鐘單調上升、p95 從 180ms 到 1.2s、TCP ephemeral ports 接近耗盡」一起丟進 Prompt,模型才有資格把假設從「可能有 leak」收斂成「先查 HttpClient 生命週期與 socket 狀態」。若只有程式碼、沒有 RSS 與 port 數,答案必須停在「待量測」。
I/O 瓶頸另一個高頻模式是 N+1。應用層迴圈看起來很短,資料庫卻被打成數百次 round-trip,p95 與 DB CPU 會同時變差,但 RSS 不一定上升。這時 ChatGPT 的價值是指出「這是 I/O 放大,不是 Memory Leak」,並要求 EXPLAIN ANALYZE 與一次查詢的 round-trip 計數。
// N+1:每個訂單再查一次顧客
var orders = await db.Orders.Where(o => o.Day == today).ToListAsync();
foreach (var order in orders)
{
order.Customer = await db.Customers.FindAsync(order.CustomerId);
}
// 一次取出:把 round-trip 從 N+1 收成 1
var ordersWithCustomer = await db.Orders
.Where(o => o.Day == today)
.Include(o => o.Customer)
.ToListAsync();
對應的 PostgreSQL 驗證不是「覺得比較快」,而是:
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.*, c.*
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.day = DATE '2026-08-17';
Python 服務同樣適用同一套工作流。無界記憶體快取(靜態 list / dict 只進不出)會讓 RSS 隨流量單調上升;未關閉的檔案 handle 則較常先打中 OSError: Too many open files。py-spy 看 CPU 熱點,tracemalloc 看分配,作業系統則用開啟檔案數驗證 handle。模型仍然只負責假設,不負責宣布「已修好」。若 RSS 只在尖峰跳一下又回到基線,那是短暫分配而非 leak,Prompt 應要求先看斜率是否單調,再決定要不要提 IDisposable 或快取。把「成長持續多久、是否隨 request 數線性增加」寫進假設的否證條件,可避免把 GC 前的峰值誤判成必須重寫。
建議的驗證迴圈:
| 步驟 | 人做的事 | ChatGPT 做的事 |
|---|---|---|
| Baseline | 記錄 RSS、p95、slow query | 禁止給出重寫 |
| Hypothesis | 提供 30–80 行熱點 | 產出可否證假設 |
| Measure | dotMemory / py-spy / EXPLAIN | 解讀數字是否支持假設 |
| Change | 只改被證據支持的那一段 | 產出最小 diff 與回歸測試 |
| Compare | 同一觀測窗口再量一次 | 判定假設成立或撤銷 |
注意事項與常見失敗 (Pitfalls)
EXPLAIN ANALYZE 時,仍會講得像已定位。修法:Prompt 寫死「沒有 required_measurements 的結果,不得使用 leak / bottleneck 當結論」;dotMemory、PerfView、py-spy、dotnet-counters、PostgreSQL pg_stat_statements 仍是主工具。using HttpClient 當萬靈丹: 這會把 Memory Leak 故事轉成 socket exhaustion,p95 更差。修法:量 HttpClient 實例數、TIME_WAIT、DNS 變更是否被長連線快取;.NET 用 IHttpClientFactory。static Dictionary 只進不出,先加 eviction / size cap,不要先擴充基礎設施。修法:先證明 hit rate、entry count、過期策略,再談外部快取。本日總結 (Takeaways)
HttpClient / IDisposable、檔案 handle、無界快取、N+1 是高優先驗證點,不是自動結論。EXPLAIN ANALYZE 才給「是不是真的」。明日預告 (Next)
明天把同樣的「模組化 Prompt」用在專案起步:自動化開發環境搭建 (Dev Environment Setup) 的 Prompt 模組庫,一次產出可跑的 docker-compose、.env.example 與 Codex 用的 AGENTS.md。